go run ./cmd/seed -confirm-dev -profile alpha fills an empty database with the plan's dataset: 100 users (1/2/5/92 across the four roles), 12 channels (10 text + 2 voice; 3 role-override, 2 user-override, 1 archived), 20,000 messages over 30 simulated days on a diurnal curve (exactly 15% in DMs across 40 pairs), 300 attachment rows (60/10/10/20%, 10KB-5MB), 500 reactions, 30 invites (10 revoked), one disabled plugin row. Deterministic by construction — fixed seed, fixed clock, constant bcrypt hash, explicit ids and timestamps, VACUUM INTO as the canonical bytes — and TestAlphaProfileByteIdentical holds the property (two full runs compared byte for byte; a schema_versions wall-clock leak was the one leak found, now pinned by the scrub). Two constants deliberately leave no rows and say why in the package comment: voice sessions are LiveKit-ephemeral, and the replay log is empty exactly as on a server restarted for an upgrade. The committed snapshot (3.2MB, under the 5MB LFS line) is the scrubbed VACUUM of that profile at the alpha.4 migration set - the schema has not moved since the tag, so it is a true alpha.4 artifact. scrub.sql beside it also anonymises a real donated database. db/alpha_snapshot_test.go is the standing canary: provenance (31 applied migrations), HEAD migrations apply cleanly, and every promised row count checks out, FTS included. Consumers (B4 HP-4, B6 upgrade rehearsal, B10 in-place upgrade) are named in the snapshot README and docs/deployment.md. Claude-Session: https://claude.ai/code/session_01B8dwVLEihnGZYtH9X631F4 Co-authored-by: Claude <noreply@anthropic.com>
4.8 KiB
OwnCord Server (Go)
Go 1.26, module github.com/J3vb/OwnCord/Server. Key deps: chi (HTTP), koanf
(config), sqlc-generated SQLite layer, LiveKit server SDK, coraza WAF,
prometheus.
Layout
api/REST handlers ·ws/WebSocket hub ·auth/sessions/TOTP ·permissions/role checks ·service/domain logic shared by both entry pointsdb/hand-written query wrappers;db/dbgen/is generated (seedb-change)cmd/executable tooling, onepackage mainper subdirectory —cmd/genprotocol/regenerates the protocol constants fromprotocol/schema.json,cmd/seed/fills a dev database (go run ./cmd/seed -confirm-dev; add-profile alphafor the deterministic B3-7 dataset behindServer/testdata/snapshots/v1.2.0-alpha.4.sqlite— regenerate that snapshot only deliberately, per its README),cmd/dbinventory/prints thedb-importer table fordocs/architecture/server-boundaries.md(exits 1 on an unlisted importer).cmd/gendocs/rewrites the route, table and config-key index blocks indocs/and must be run asgo run -tags otel,wazero ./cmd/gendocs(make docs-verifyfails on drift).scripts/holds shell/JS tooling only; no Go entry point lives thereadmin/web admin panel ·updater/self-update + signature verification ·plugin/WASM plugin runtime (-tags wazero) ·telemetry/OTel (-tags otel)syncutil/lock helpers that gain deadlock detection under-tags deadlock
Gotchas
- Build tags gate whole files, so all four variants must compile: default,
-tags otel,-tags wazero,-tags otel,wazero. Tests must also pass under-raceand under-tags deadlock. Theci-checkskill has the commands. admin/static/index.htmlis server-owned, and its invariants are locked from two places: text-level ones fromadmin/perm_grid_test.goandadmin/emoji_section_test.go, execution-level ones fromClient/tests/contract/— Go has no JS engine, so a Go port could only grep. Do not "fix" that split by rewriting the contract test as a regex.wsis the hub: broadcast fan-out, per-client send queues, replay, and voice state all interact under several locks. Sequenced frames share one per-client FIFO because clients ack onlymax(seq)— a frame that skips the queue, or a seq allocated for a frame that is then dropped, is silently unrecoverable.- Prefer the standard library.
syncutilexists so lock usage is uniform and detectable; do not hand-roll around it.Server/invariants/enforces this atgo testtime; exceptions are greppable viagrep -rn "invariant:allow" Server/. - Only
db/andservice/importdbfreely. Any other production file that imports it needs a row ininvariants/db_import_boundary.go(DBImportAllow) with a disposition and reason — the B3 inventory, which only shrinks. New persistence goes behind a service, not into a handler. - Only
permissions/calls the raw permission bit helpers (HasPerm,HasAnyPerm,HasServerPerm,HasAdmin,EffectivePerms,EffectiveChannelPerms). Everywhere else resolves apermissions.Subjectand asks the predicate that owns the property (CanViewChannel,CanAdmitSession,CanSendMessage,CanType,CanJoinVoice,CanModerateVoice) — one predicate per security property, so a call site cannot re-derive half a rule. The residue that predates B2-5 is listed by symbol ininvariants/authz_chokepoint.go(AuthzResidueAllow) with a class, a reason, and the exact helper calls it is frozen at — a row is an inventory, not a licence for the function, so a second raw call inside a listed one still fails. That list only shrinks too.
Coverage floor
coverage-floor.json holds the aggregate floor and one floor per core package
(ws, service, permissions, auth, db); db/dbgen and cmd/ are
excluded there because they are generated or entry points, and an exclusion is
spelled without a trailing slash (cmd, not cmd/). CI checks it on the Linux
leg, after the test steps that share the job. Locally, from Server/:
go test -race ./... -coverprofile=coverage.out -cover
bash scripts/coverage-floor.sh coverage.out
Ratchet. A floor is the lowest Linux figure observed for that package,
truncated to 0.1, minus 0.1 where the package varied between runs — ws
and the aggregate do vary, because a few -race branches in ws are
timing-dependent and move four or so statements per run. A PR that raises a
figure raises that floor in the same PR; the number in the file is what the
branch measured, not a stale one. Nobody lowers a floor without a hold-point
(HP) entry recording why. Coverage also differs between the Linux and Windows
legs, so the floors track the Linux figure and the check runs only there — on
Windows the script will report aggregate and ws under floor, by design.