Files
galaxy-game/CLAUDE.md
T
Ilia Denisov a9087691a3
Tests · Go / test (push) Successful in 2m6s
Tests · Go / test (pull_request) Successful in 3m1s
Tests · Integration / integration (pull_request) Successful in 1m42s
chore(ci): tidy CI/dev infra — drop local-ci, lift migration rule, scope by galaxy.stack label
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>
2026-05-18 23:32:42 +02:00

202 lines
9.8 KiB
Markdown

# 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 of `docs/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 to `docs/FUNCTIONAL.md` must also be
mirrored into `docs/FUNCTIONAL_ru.md` in 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.Fatal` on
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.yaml` and `*.proto` files — 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 in `README.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 from `development`. A merge fires
`prod-build.yaml` which packages the artifacts; production rollout
is manual through `deploy-prod.yaml`.
- `development` — long-lived dev integration branch. Every merge into
it auto-deploys to the dev environment via `dev-deploy.yaml`
(reachable at `https://www.galaxy.lan` / `https://api.galaxy.lan`).
- `feature/*` — short-lived branches off `development`. 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:
1. Commit the stage changes on the feature branch.
2. `git push gitea …` to publish the branch.
3. Poll the latest run in the Gitea UI (or the API) until it leaves
`running`. Inspect the log on failure.
4. Only after every workflow that fired is `success` may the stage be
marked done in the corresponding `PLAN.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:
1. **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.
2. **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.
3. **Live documentation.** Module `README.md`, project
`docs/ARCHITECTURE.md`, `docs/FUNCTIONAL.md` (with its
`docs/FUNCTIONAL_ru.md` mirror), the affected service `openapi.yaml`
or `*.proto`, and any topic doc under `galaxy/<service>/docs/` that
the decision touches. `README.md` and `ARCHITECTURE.md` always
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 `.md` or `.yaml` file, 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.md`
or `galaxy/<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 relevant `openapi.yaml` or `*.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.md` changes, mirror the same change into
`docs/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.md` will 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`.