Five connected cleanups across the dev/CI infrastructure:
1. Drop tools/local-ci/. The standalone Gitea + act_runner stack was
the legacy "offline workflow validator"; the per-stage CI gate now
runs on gitea.lan and the directory was only retained as a
fallback. Removing it leaves no operational dependency: backend,
gateway, and game code have no references; documentation that
pointed at it (CLAUDE.md, docs/ARCHITECTURE.md, ui/docs/testing.md,
tools/dev-deploy/README.md, tools/local-dev/README.md) is updated
in this same change. Historical "Verified on local-ci run N"
markers in ui/PLAN.md are preserved unchanged.
2. Lift the pre-production single-migration rule. The rule forced
every schema delta into 00001_init.sql and required a manual
make clean-data wipe on every backward-incompatible change in
tools/dev-deploy/. Future schema deltas now land as additive
sequence-numbered files (00002_*.sql, …) that goose applies
automatically on backend startup; 00001_init.sql becomes an
immutable baseline. Authoring conventions live in
backend/internal/postgres/migrations/README.md. The chain may be
squashed back into a fresh 00001 as a deliberate one-time
operation before the first production deployment.
3. Document the deployment cadence. The dev environment is
single-tenant: pushes to feature/* run the test workflows
(go-unit, ui-test, integration) only; dev-deploy.yaml fires on
push to development. A workflow_dispatch override on
dev-deploy.yaml lets a developer preview a feature branch on the
shared dev environment before merge; the next merge into
development overwrites the manual deploy idempotently.
4. Scope compose-managed resources by an explicit
galaxy.stack=<local-dev|dev-deploy> label. Both compose files
stamp the label on every service, network, and named volume.
Makefiles in tools/local-dev/ and tools/dev-deploy/ filter their
engine-cleanup operations by (stack-label AND engine OCI title)
so they never touch unrelated workloads on the same daemon.
dev-deploy.yaml gains a pre-`compose up` step that reaps stale
exited/dead containers under the dev-deploy stack label.
5. Backend now stamps the same galaxy.stack=<value> label on every
engine container it spawns, sourced from a new BACKEND_STACK_LABEL
env var (empty → label not applied; legacy-safe). Both compose
files set it to their stack name (local-dev / dev-deploy). The
contract is recorded in docs/ARCHITECTURE.md under
"Container labels". A package-level test in
backend/internal/runtime exercises both the label-present and
label-absent paths.
No tests intentionally regressed: go test ./backend/internal/{config,
runtime,dockerclient} is green, both compose files validate cleanly,
and the backend, gateway, and game modules all build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.8 KiB
Galaxy Game — Project Conventions
This repository hosts the Galaxy Game project.
Sources of truth
docs/ARCHITECTURE.md— global architecture, security model, cross-service contracts, and project-wide rules.docs/FUNCTIONAL.md— per-domain user stories that describe what each user-visible operation does, with the exact gateway and backend logic for it. Starting point for any change request that touches behaviour.docs/FUNCTIONAL_ru.md— Russian translation ofdocs/FUNCTIONAL.md, maintained as a convenience for the project owner. Not a source of truth — when the two files disagree, the English version wins. Every point edit applied todocs/FUNCTIONAL.mdmust also be mirrored intodocs/FUNCTIONAL_ru.mdin the same patch (translate the changed paragraphs only, do not re-translate the whole file). A full re-translation only happens on explicit owner request.docs/TESTING.md— testing layers (unit / integration), the integration runbook, and the principles every test must follow (no-op observability for testcontainers,t.Fatalon infrastructure breakages, label-driven preclean). Read before adding tests or modifying the integration harness.galaxy/<service>/README.md— service conventions, layout, configuration, and operations for an implemented or planned service.galaxy/<service>/openapi.yamland*.protofiles — exact wire contracts for REST and gRPC surfaces.
Planning of service implementation and Implementing Plan
galaxy/<service>/PLAN.md— staged implementation plan for the service. May be already complete and resides for historical reasons.galaxy/<service>/docs/— live topic-based documentation that's deeper than what fits inREADME.md(per-feature design notes, protocol specs, runbooks). Not stage-by-stage history.
Branching and CI flow
Branches:
main— production-track. Direct pushes are disallowed; the only way in is a PR merge fromdevelopment. A merge firesprod-build.yamlwhich packages the artifacts; production rollout is manual throughdeploy-prod.yaml.development— long-lived dev integration branch. Every merge into it auto-deploys to the dev environment viadev-deploy.yaml(reachable athttps://www.galaxy.lan/https://api.galaxy.lan).feature/*— short-lived branches offdevelopment. Merged back via PR; only then do they reach the dev environment automatically.
Workflows in .gitea/workflows/:
| File | Trigger | What it does |
|---|---|---|
go-unit.yaml |
push + PR matching Go paths | Fast Go unit tests. |
ui-test.yaml |
push + PR matching ui/** |
Vitest + Playwright. |
integration.yaml |
PR to development/main; push to development |
testcontainers integration suite. |
dev-deploy.yaml |
push to development; workflow_dispatch on any ref |
Build images + (re)deploy to tools/dev-deploy/. |
prod-build.yaml |
push to main |
Build prod images and docker save into artifacts. |
deploy-prod.yaml |
workflow_dispatch |
Manual rollout (placeholder until prod host exists). |
Deployment cadence
The long-lived dev environment (tools/dev-deploy/) is single-tenant:
one live deployment, redeployed on every merge into development.
While a PR is open the dev environment stays on whatever was last
merged — pushes to feature/* only fire the test workflows
(go-unit, ui-test, integration), not dev-deploy.yaml.
To preview an unmerged feature branch on the shared dev environment,
trigger dev-deploy.yaml manually from the Gitea UI
(Actions → Deploy · Dev → Run workflow) and pick the feature ref.
The deploy is idempotent: the next merge into development simply
overwrites whatever the manual dispatch left behind.
Per-stage CI gate
Every completed stage from any PLAN.md (per-service or ui/PLAN.md)
must be exercised on gitea.lan before being declared done. The
short version:
- Commit the stage changes on the feature branch.
git push gitea …to publish the branch.- Poll the latest run in the Gitea UI (or the API) until it leaves
running. Inspect the log on failure. - Only after every workflow that fired is
successmay the stage be marked done in the correspondingPLAN.md.
Decisions during stage implementation
Stages from PLAN.md produce decisions. Those decisions never live in a
separate per-decision history file. Instead, every non-obvious decision is
baked back into the live state in three places:
- The plan itself. Update the relevant stage's text, acceptance criteria, or targeted tests so it reflects what was decided. If earlier already-implemented stages need to follow the new agreement, correct their code, tests, and live docs in the same patch.
- Later, not-yet-implemented stages. When a decision affects later stages — scope, dependencies, deliverables, or tests — update those stages now, do not leave the future to re-derive them.
- Live documentation. Module
README.md, projectdocs/ARCHITECTURE.md,docs/FUNCTIONAL.md(with itsdocs/FUNCTIONAL_ru.mdmirror), the affected serviceopenapi.yamlor*.proto, and any topic doc undergalaxy/<service>/docs/that the decision touches.README.mdandARCHITECTURE.mdalways describe current state, not the history of how it was reached.
Scope of PLAN.md changes
The existing codebase of galaxy/<service> may be modified or extended when a
plan stage requires it. All such changes must be covered by new or updated tests
and reflected in documentation when they affect documented behavior.
Migrations
Schema changes for backend go into a new 0000N_*.sql file under
backend/internal/postgres/migrations/ with a monotonically increasing
prefix. 00001_init.sql is the historical baseline and stays
immutable; every subsequent change is its own additive migration with
matching Up/Down sides. pressly/goose/v3 (embedded into the backend
binary) applies pending migrations on startup, so the long-lived dev
environment picks up schema deltas without a manual reset.
Before the first production deployment the migration chain may be
squashed back into a single fresh 00001_init.sql for a clean slate;
plan that work as an explicit task when it lands. See
backend/internal/postgres/migrations/README.md for the local
authoring conventions (file naming, transactional vs. non-transactional
sections, backward-compatible deletes, rollback expectations).
Documentation discipline
- Code and docs are kept in sync. If an implementation changes behavior
described in a
.mdor.yamlfile, update that file in the same patch. - If existing docs are incomplete or wrong for behavior you are already touching, fix them in the same patch.
- Do not silently remove commitments from
galaxy/<service>/README.mdorgalaxy/<service>/docs/*.md. When a rule changes, either update it in place with the new agreement, or move the section to a more appropriate doc with a reference kept. - Cross-module impact: if a new agreement requires changes in
already-implemented modules, make those changes — code, tests, docs — in
the same patch, and record the new rule in
docs/ARCHITECTURE.md.
Documentation synchronisation
The same behaviour is described in several parallel sources: code,
docs/ARCHITECTURE.md, docs/FUNCTIONAL.md (with its Russian mirror
docs/FUNCTIONAL_ru.md), the affected service README.md, the
relevant openapi.yaml or *.proto, and the topic-based docs under
galaxy/<service>/docs/. They must never disagree.
- Any patch that changes user-visible behaviour, an API contract, or a cross-service flow updates every affected source in the same change set — never one source in this patch and another later.
- Before declaring a change complete, read the relevant sections of
docs/ARCHITECTURE.md,docs/FUNCTIONAL.md, the affected service README, the relevantopenapi.yamlor*.proto, and the implementing code; confirm they describe the same behaviour. - When two sources disagree about existing behaviour, do not pick one silently. Decide which one is authoritative, fix the contradiction in the same patch, and call out the change in the response. If the resolution is non-obvious, escalate to the user before proceeding.
- When touching code, also re-read inline package and Go Doc Comments in the affected packages and update them when they no longer match the code.
- When
docs/FUNCTIONAL.mdchanges, mirror the same change intodocs/FUNCTIONAL_ru.md(translate only the touched paragraphs). Skipping the mirror is treated as an incomplete patch.
Code compactness
- Prefer compact code over speculative universality. Three similar occurrences are not yet a pattern — wait for the third real caller before extracting an abstraction.
- Do not add seams, hooks, or configuration knobs for hypothetical
future requirements. If the next stage of
PLAN.mdwill need something, the next stage will add it. - A bug fix does not need surrounding cleanup; a one-shot operation does not need a helper function; a single concrete value does not need a parameter.
- When the plan can be satisfied by reusing an existing function or type, do that instead of introducing a new one.
- This rule is about scope, not laziness — well-named identifiers, precise types, and full test coverage stay non-negotiable.
Dependencies
- Before adding a new module, check its upstream repository for the latest stable version and use that.
- When a well-maintained library clearly outperforms stdlib for a concrete need, do not adopt it silently — propose a short list of 1+ candidates for the user to pick. Default remains stdlib.
Language
- All code, comments, identifiers, commit messages, docs, and filenames are written in English.
- User-facing chat responses follow the Russian-translation rule from the
user-level
CLAUDE.md.