Stage 14: solver & dictionary split — consume published module + DAWG artifact (TODO-1/TODO-2)
- backend/go.mod pins gitea.iliadenisov.ru/developer/scrabble-solver v1.0.0; the engine's imports use the published module path; go.work drops the solver replace (GOPRIVATE fetches it directly from Gitea). The solver's wordlist/dictdawg are now public packages. - CI (go-unit, integration): drop the solver sibling-clone, set GOPRIVATE, and download the dictionary DAWG release artifact (scrabble-dawg-<DICT_VERSION>.tar.gz from the new scrabble-dictionary repo) for BACKEND_DICT_DIR. - Docs: ARCHITECTURE §5/§11/§13/§14 + backend/README updated to the published-module + release-artifact model. PLAN.md re-scoped Stage 14 to the split and added Stages 15 (deploy infra & test contour), 16 (prod contour), 17 (dual Telegram bots); TODO-1/TODO-2 marked done.
This commit is contained in:
@@ -33,16 +33,25 @@ jobs:
|
|||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
# The engine consumes the published scrabble-solver module from this Gitea;
|
||||||
|
# GOPRIVATE makes go fetch it directly (skipping the public proxy/checksum DB).
|
||||||
|
# DICT_VERSION selects the dictionary DAWG release the engine tests load.
|
||||||
|
GOPRIVATE: gitea.iliadenisov.ru/*
|
||||||
|
DICT_VERSION: v1.0.0
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Fetch scrabble-solver (sibling)
|
- name: Fetch dictionary DAWGs
|
||||||
# The engine package consumes scrabble-solver in-process; go.work points
|
# The DAWGs moved to the scrabble-dictionary repo (the solver is now a
|
||||||
# its bare module path at this sibling checkout. The repository is public,
|
# versioned module pinned in backend/go.mod, fetched via GOPRIVATE — no
|
||||||
# so the clone needs no credentials. It tracks master HEAD (see PLAN.md
|
# sibling clone). They ship as a release artifact, one semver per set.
|
||||||
# TODO-1 for the move to a published, versioned module).
|
run: |
|
||||||
run: git clone --depth 1 https://gitea.iliadenisov.ru/developer/scrabble-solver.git "${GITHUB_WORKSPACE}/../scrabble-solver"
|
mkdir -p "${GITHUB_WORKSPACE}/dawg"
|
||||||
|
curl -fsSL -o /tmp/dawg.tar.gz "https://gitea.iliadenisov.ru/developer/scrabble-dictionary/releases/download/${DICT_VERSION}/scrabble-dawg-${DICT_VERSION}.tar.gz"
|
||||||
|
tar xzf /tmp/dawg.tar.gz -C "${GITHUB_WORKSPACE}/dawg"
|
||||||
|
ls -la "${GITHUB_WORKSPACE}/dawg"
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
@@ -66,7 +75,7 @@ jobs:
|
|||||||
- name: test
|
- name: test
|
||||||
# -count=1 disables the test cache so a green run never depends on a
|
# -count=1 disables the test cache so a green run never depends on a
|
||||||
# previous runner's cached state. BACKEND_DICT_DIR points the engine
|
# previous runner's cached state. BACKEND_DICT_DIR points the engine
|
||||||
# tests at the committed DAWGs in the sibling checkout.
|
# tests at the DAWGs fetched from the dictionary release.
|
||||||
env:
|
env:
|
||||||
BACKEND_DICT_DIR: ${{ github.workspace }}/../scrabble-solver/dawg
|
BACKEND_DICT_DIR: ${{ github.workspace }}/dawg
|
||||||
run: go test -count=1 ./backend/... ./pkg/... ./gateway/... ./platform/telegram/...
|
run: go test -count=1 ./backend/... ./pkg/... ./gateway/... ./platform/telegram/...
|
||||||
|
|||||||
@@ -35,16 +35,25 @@ jobs:
|
|||||||
# Ryuk (testcontainers' reaper) does not start cleanly on every runner;
|
# Ryuk (testcontainers' reaper) does not start cleanly on every runner;
|
||||||
# the suite's TestMain terminates its own container, so disable it.
|
# the suite's TestMain terminates its own container, so disable it.
|
||||||
TESTCONTAINERS_RYUK_DISABLED: "true"
|
TESTCONTAINERS_RYUK_DISABLED: "true"
|
||||||
|
# The engine consumes the published scrabble-solver module from this Gitea
|
||||||
|
# (GOPRIVATE -> direct fetch, skipping the public proxy/checksum DB);
|
||||||
|
# DICT_VERSION selects the dictionary DAWG release the engine tests load.
|
||||||
|
GOPRIVATE: gitea.iliadenisov.ru/*
|
||||||
|
DICT_VERSION: v1.0.0
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Fetch scrabble-solver (sibling)
|
- name: Fetch dictionary DAWGs
|
||||||
# The backend now imports the engine package, which consumes
|
# The DAWGs moved to the scrabble-dictionary repo (the solver is now a
|
||||||
# scrabble-solver in-process; go.work points its bare module path at this
|
# versioned module pinned in backend/go.mod, fetched via GOPRIVATE — no
|
||||||
# sibling checkout. The repository is public, so the clone needs no
|
# sibling clone). They ship as a release artifact; the engine's untagged
|
||||||
# credentials. It tracks master HEAD (see PLAN.md TODO-1).
|
# tests (compiled here too) load them.
|
||||||
run: git clone --depth 1 https://gitea.iliadenisov.ru/developer/scrabble-solver.git "${GITHUB_WORKSPACE}/../scrabble-solver"
|
run: |
|
||||||
|
mkdir -p "${GITHUB_WORKSPACE}/dawg"
|
||||||
|
curl -fsSL -o /tmp/dawg.tar.gz "https://gitea.iliadenisov.ru/developer/scrabble-dictionary/releases/download/${DICT_VERSION}/scrabble-dawg-${DICT_VERSION}.tar.gz"
|
||||||
|
tar xzf /tmp/dawg.tar.gz -C "${GITHUB_WORKSPACE}/dawg"
|
||||||
|
ls -la "${GITHUB_WORKSPACE}/dawg"
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
@@ -56,7 +65,7 @@ jobs:
|
|||||||
# -count=1 disables the test cache; -p=1 -parallel=1 keeps the
|
# -count=1 disables the test cache; -p=1 -parallel=1 keeps the
|
||||||
# container-backed tests serial; the 15-minute timeout bounds a stuck
|
# container-backed tests serial; the 15-minute timeout bounds a stuck
|
||||||
# container pull. The engine package's (untagged) tests also compile and
|
# container pull. The engine package's (untagged) tests also compile and
|
||||||
# run here, so BACKEND_DICT_DIR points them at the committed DAWGs.
|
# run here, so BACKEND_DICT_DIR points them at the DAWGs from the release.
|
||||||
env:
|
env:
|
||||||
BACKEND_DICT_DIR: ${{ github.workspace }}/../scrabble-solver/dawg
|
BACKEND_DICT_DIR: ${{ github.workspace }}/dawg
|
||||||
run: go test -tags=integration -count=1 -p=1 -parallel=1 -timeout=15m ./backend/...
|
run: go test -tags=integration -count=1 -p=1 -parallel=1 -timeout=15m ./backend/...
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ independent (see ARCHITECTURE §9.1).
|
|||||||
| 11 | Account linking & merge | **done** |
|
| 11 | Account linking & merge | **done** |
|
||||||
| 12 | Observability & performance (telemetry, metrics, guest GC) | **done** |
|
| 12 | Observability & performance (telemetry, metrics, guest GC) | **done** |
|
||||||
| 13 | Alphabet on the wire (UI alphabet-agnostic) | **done** |
|
| 13 | Alphabet on the wire (UI alphabet-agnostic) | **done** |
|
||||||
| 14 | CI & deploy (multi-service, dictionary artifacts) | todo |
|
| 14 | Solver & dictionary split (publish solver + scrabble-dictionary repo/artifact) | **done** |
|
||||||
|
| 15 | Deploy infra & test contour (Dockerfiles, gateway static UI, compose, observability) | todo |
|
||||||
|
| 16 | Prod contour deploy (SSH export/import, manual after merge) | todo |
|
||||||
|
| 17 | Dual Telegram bots & language-gated variants | todo |
|
||||||
|
|
||||||
Scaffolding is incremental: `go.work` lists only existing modules; each stage
|
Scaffolding is incremental: `go.work` lists only existing modules; each stage
|
||||||
adds the modules it needs.
|
adds the modules it needs.
|
||||||
@@ -213,7 +216,7 @@ new `pkg/telemetry`; add telemetry to the **gateway** and the **Telegram connect
|
|||||||
domain/operational **metrics** close to the business (game replay/validate timings,
|
domain/operational **metrics** close to the business (game replay/validate timings,
|
||||||
started/abandoned games, live-cache size, chat/nudge counts, the edge roundtrip, Go
|
started/abandoned games, live-cache size, chat/nudge counts, the edge roundtrip, Go
|
||||||
runtime metrics); discharge **TODO-3** (abandoned-guest GC). The OTLP collector and
|
runtime metrics); discharge **TODO-3** (abandoned-guest GC). The OTLP collector and
|
||||||
dashboards are stood up with the deploy (Stage 14); the default exporter stays `none`,
|
dashboards are stood up with the deploy (Stage 15); the default exporter stays `none`,
|
||||||
so CI needs no collector. Performance is operational-metric instrumentation, not
|
so CI needs no collector. Performance is operational-metric instrumentation, not
|
||||||
speculative optimisation (the standing "evidence first" rule — no measured hotspot yet).
|
speculative optimisation (the standing "evidence first" rule — no measured hotspot yet).
|
||||||
Open details: exporter default and whether a collector is stood up now; the metric set
|
Open details: exporter default and whether a collector is stood up now; the metric set
|
||||||
@@ -239,35 +242,69 @@ Open details: the fbs shape and `include_alphabet` flag placement; whether to ke
|
|||||||
concrete-letter fields during the transition; whether tile exchange moves fully to
|
concrete-letter fields during the transition; whether tile exchange moves fully to
|
||||||
indices; the premiums.ts parity-test rework.
|
indices; the premiums.ts parity-test rework.
|
||||||
|
|
||||||
### Stage 14 — CI & deploy
|
### Stage 14 — Solver & dictionary split (TODO-1 + TODO-2)
|
||||||
Scope: the full **multi-service production deploy** plus the observability backend, also
|
Re-scoped from the original "CI & deploy": that was several sessions of work, so the
|
||||||
discharging **TODO-1** and **TODO-2**. Backend + gateway **Dockerfiles** (multi-stage
|
deploy + observability + the two-bots idea were split into **Stages 15–17** below and this
|
||||||
distroless, mirroring the Stage 9 connector image); the gateway gains **static UI
|
stage took only the dependency/artifact split that everything else builds on. Scope: publish
|
||||||
serving** (the §13 single-origin model — mini-landing at `/`, Mini App under
|
`scrabble-solver` as a versioned Gitea module and split the dictionary build into a new
|
||||||
`/telegram/`), documented since Stage 9 but **not yet implemented**; prod UI build vars
|
`scrabble-dictionary` repo delivering a **release artifact**, then make `scrabble-game` consume
|
||||||
(`VITE_TELEGRAM_BOT_ID` for the Login Widget, the Mini App URL / share link); a root
|
both — discharging **TODO-1** and **TODO-2**.
|
||||||
`deploy/docker-compose.yml` (backend + gateway + Postgres + connector + the OTLP
|
|
||||||
collector / Grafana stack) on the external `edge` network behind the host caddy, the VPN
|
- **TODO-1 — solver published.** `scrabble-solver` renamed to module
|
||||||
sidecar only for the connector; a **deploy workflow** mirroring `../15-puzzle` (host-mode
|
`gitea.iliadenisov.ru/developer/scrabble-solver`, tagged **v1.0.0**; `wordlist`/`dictdawg`
|
||||||
runner, `docker compose up -d --build`, no external registry, env from Gitea secrets, a
|
de-internalised to public packages (the dict repo imports them); `cmd/builddict`/`dictprep`/the
|
||||||
post-deploy probe). Stand up the **OTLP collector + dashboards** (the export wiring landed
|
`dictionaries` submodule moved out; `internal/dict` repointed at the committed `dawg/*.dawg`
|
||||||
in Stage 12).
|
fixtures. `backend/go.mod` pins `v1.0.0`; the `go.work` replace and the CI sibling-clone are
|
||||||
- **TODO-1 — publish & version the solver:** tag/publish `scrabble-solver`, drop the
|
gone; `GOPRIVATE=gitea.iliadenisov.ru/*` makes go fetch it directly (no public proxy/checksum DB).
|
||||||
`go.work` replace + the CI clone, pin a version in `backend/go.mod` (or keep cloning the
|
- **TODO-2 — dictionary artifacts.** New repo `developer/scrabble-dictionary` holds the word-list
|
||||||
sibling as the minimal-diff fallback). The DAWGs are delivered separately regardless.
|
sources + `cmd/builddict` and builds the three DAWGs against the **published solver + pinned
|
||||||
- **TODO-2 — versioned dictionary artifacts:** a **new versioned repo** for the wordlist
|
`dafsa`/`alphabet` v1.1.0**, so they are byte-identical to the solver's fixtures (no index drift).
|
||||||
parsers + built DAWGs, delivered as a **release artifact** (Gitea release / OCI / object
|
Released as `scrabble-dawg-vX.Y.Z.tar.gz` (flat, one semver per set); the Go workflows download it
|
||||||
store — not `go get`; DAWGs are data). **One semver label `vX.Y.Z` for the whole set**,
|
and point `BACKEND_DICT_DIR` at it. The runtime contract is unchanged (additive
|
||||||
additive: a deploy drops a new `BACKEND_DICT_DIR/<version>/` subdir;
|
`BACKEND_DICT_DIR/<version>/`, `engine.OpenWithVersions`, per-game `dict_version` pin; a version is
|
||||||
`engine.OpenWithVersions` loads every present subdir at boot; `BACKEND_DICT_VERSION`
|
safe to retire once no active game pins it).
|
||||||
selects the default for **new** games. A new version never breaks a running backend
|
|
||||||
(each game pins its `dict_version`; versions are additive); **only active games need a
|
### Stage 15 — Deploy infra & test contour
|
||||||
dictionary** (validate-at-submit — finished games replay the dictionary-independent
|
Scope: the deploy machinery + the **test contour** (the bulk of the original Stage 14). Backend +
|
||||||
journal), so a version is safe to retire once no active game pins it. The dict repo must
|
gateway **Dockerfiles** (multi-stage distroless, mirroring the Stage 9 connector image); the gateway
|
||||||
build against the **same `dafsa`/`alphabet`/solver** the backend runs, or letter indexing
|
gains **static UI serving** — **embedded** via `go:embed` (a node build stage in the gateway image),
|
||||||
drifts (ties into Stage 13).
|
SPA served at both `/` (web) and `/telegram/` (Mini App), the §13 single-origin model; prod UI build
|
||||||
Open details: embed-vs-mount for the UI build and the DAWG set; the OTLP collector /
|
vars (`VITE_TELEGRAM_BOT_ID`, `VITE_TELEGRAM_LINK`, `VITE_GATEWAY_URL`) as image build-args; a root
|
||||||
dashboard stack; solver-publish vs clone-in-build; load expectations.
|
`deploy/docker-compose.yml` (backend + gateway + Postgres + connector + VPN sidecar + the **full
|
||||||
|
observability stack** — OTel Collector + Prometheus + Tempo + Grafana with provisioned dashboards) on
|
||||||
|
the external `edge` network behind the host caddy (VPN sidecar only for the connector); the backend
|
||||||
|
image pulls the DAWG release artifact (Stage 14). **The test contour deploys automatically on push to
|
||||||
|
a feature branch** (`docker compose up -d --build` on the local host where the gitea runner lives),
|
||||||
|
with a post-deploy probe (`GET /` on the gateway). Test-contour secrets use the **`TEST_`** prefix
|
||||||
|
(see Stage 16).
|
||||||
|
Open details (re-interview at start): the dashboard set; the gateway static-serving hook (before the
|
||||||
|
h2c wrap — `/` + `/telegram/` mounts; a committed `dist` placeholder so `go build` works without a UI
|
||||||
|
build); Postgres healthcheck/volume; whether the connector-scoped compose is retired for the root one;
|
||||||
|
collector/Tempo/Prometheus retention.
|
||||||
|
|
||||||
|
### Stage 16 — Prod contour deploy
|
||||||
|
Scope: the **production contour** on a remote host over SSH. Deploy by **container export/import**
|
||||||
|
(`docker save` → `scp`/ssh → `docker load` → `docker compose up` on the remote), the SSH key + host IP
|
||||||
|
in Gitea secrets; **strictly manual** (`workflow_dispatch`) after a feature branch is merged to
|
||||||
|
`master`. Two-contour config uses **`TEST_`/`PROD_` secret/variable prefixes** — Gitea 1.26 has no
|
||||||
|
deployment environments (verified: the `environments` API 404s), so a flat prefixed namespace is the
|
||||||
|
convention.
|
||||||
|
Open details (re-interview): export/import vs a registry trade-off; prod domain/TLS at the remote
|
||||||
|
caddy; prod VPN; rollback.
|
||||||
|
|
||||||
|
### Stage 17 — Dual Telegram bots & language-gated variants *(feature; own interview)*
|
||||||
|
Scope (owner's idea, to design in detail at its own start): run **two bots in the one connector
|
||||||
|
container** — one for the English audience, one for Russian — each with its own token + game-channel id
|
||||||
|
+ service-language tag (the same Telegram user id spans both). `initData` validation tries each bot's
|
||||||
|
token in turn (none succeeds ⇒ invalid). The connector returns the **service language `en`/`ru`**;
|
||||||
|
`Notify`/`SendToUser` take a language key so the right bot delivers. The UI **gates the game-type
|
||||||
|
(variant) choice** by service language (en → English; ru → Russian + Эрудит).
|
||||||
|
Open details (own interview): which bot sends a notification for an **existing** game (game language vs
|
||||||
|
the player's service language) given one user id spans both bots; behaviour for **non-Telegram**
|
||||||
|
players (web/email/guest — ungated, or by interface language); the proto/wire changes
|
||||||
|
(`ValidateInitData` service-language field, a bot/language selector on the push RPCs); per-bot config +
|
||||||
|
tests. Engineering feedback already captured at the Stage 14 interview: the two-bots-in-one-container +
|
||||||
|
sequential validation + language-keyed routing model is sound.
|
||||||
|
|
||||||
## Refinements logged during implementation
|
## Refinements logged during implementation
|
||||||
|
|
||||||
@@ -862,13 +899,15 @@ dashboard stack; solver-publish vs clone-in-build; load expectations.
|
|||||||
+ performance + guest GC; **Stage 13** = alphabet-on-the-wire (TODO-4); **Stage 14** =
|
+ performance + guest GC; **Stage 13** = alphabet-on-the-wire (TODO-4); **Stage 14** =
|
||||||
CI & deploy (TODO-1, TODO-2, the collector + dashboards). The latter two were written
|
CI & deploy (TODO-1, TODO-2, the collector + dashboards). The latter two were written
|
||||||
into the plan now as the agreed baseline (each still re-interviews at its own start).
|
into the plan now as the agreed baseline (each still re-interviews at its own start).
|
||||||
|
(Stage 14 was itself later re-scoped to the solver/dictionary split alone; deploy +
|
||||||
|
observability + the dual-bot idea split into Stages 15–17.)
|
||||||
- **Shared telemetry** (interview): a new `pkg/telemetry` owns the OTel provider
|
- **Shared telemetry** (interview): a new `pkg/telemetry` owns the OTel provider
|
||||||
bootstrap (exporter selection, W3C propagators, shutdown, Go runtime metrics); the
|
bootstrap (exporter selection, W3C propagators, shutdown, Go runtime metrics); the
|
||||||
backend `internal/telemetry` is now a thin facade over it (keeping its gin middleware),
|
backend `internal/telemetry` is now a thin facade over it (keeping its gin middleware),
|
||||||
and the gateway and connector gained telemetry runtimes. A configurable **`otlp`**
|
and the gateway and connector gained telemetry runtimes. A configurable **`otlp`**
|
||||||
exporter was added alongside `none`/`stdout`; the **default stays `none`**, the OTLP
|
exporter was added alongside `none`/`stdout`; the **default stays `none`**, the OTLP
|
||||||
endpoint comes from the standard `OTEL_EXPORTER_OTLP_*` env, and the collector +
|
endpoint comes from the standard `OTEL_EXPORTER_OTLP_*` env, and the collector +
|
||||||
dashboards are Stage 14 (so CI needs none). `otelgrpc` instruments the backend push
|
dashboards are Stage 15 (so CI needs none). `otelgrpc` instruments the backend push
|
||||||
server, the gateway's backend + connector clients, and the connector's gRPC server.
|
server, the gateway's backend + connector clients, and the connector's gRPC server.
|
||||||
New config `GATEWAY_SERVICE_NAME`/`GATEWAY_OTEL_*` and `TELEGRAM_SERVICE_NAME`/
|
New config `GATEWAY_SERVICE_NAME`/`GATEWAY_OTEL_*` and `TELEGRAM_SERVICE_NAME`/
|
||||||
`TELEGRAM_OTEL_*`; the backend's existing `BACKEND_OTEL_*` gained the `otlp` value.
|
`TELEGRAM_OTEL_*`; the backend's existing `BACKEND_OTEL_*` gained the `otlp` value.
|
||||||
@@ -938,33 +977,52 @@ dashboard stack; solver-publish vs clone-in-build; load expectations.
|
|||||||
handled by construction (the running backend produces the table, so client↔server cannot
|
handled by construction (the running backend produces the table, so client↔server cannot
|
||||||
drift); the DAWG/solver build-time agreement remains **Stage 14 / TODO-2**.
|
drift); the DAWG/solver build-time agreement remains **Stage 14 / TODO-2**.
|
||||||
|
|
||||||
|
- **Stage 14** (interview + implementation, re-scoped + discharges TODO-1/TODO-2):
|
||||||
|
- **Re-scoped to the split** (interview): the original "CI & deploy" was several sessions of work,
|
||||||
|
so it was cut to the **solver/dictionary split** (the dependency foundation) and the deploy +
|
||||||
|
observability + the dual-bot idea were written into the plan as new **Stages 15–17**. The deploy
|
||||||
|
decisions taken at the interview are recorded there (embed the UI in the gateway via `go:embed`;
|
||||||
|
full Collector+Prometheus+Tempo+Grafana stack; **two contours** — test = auto on feature-branch
|
||||||
|
push on the local host, prod = manual SSH `docker save`/`load` after merge; `TEST_`/`PROD_` secret
|
||||||
|
prefixes since Gitea 1.26 has no environments — verified).
|
||||||
|
- **TODO-1 — publish solver** (interview: "опубликовать и запинить"): `scrabble-solver` renamed to
|
||||||
|
module `gitea.iliadenisov.ru/developer/scrabble-solver`, `internal/{wordlist,dictdawg}`
|
||||||
|
**de-internalised** to public packages (so the dict repo imports one builder — no drift), the build
|
||||||
|
pipeline (`cmd/builddict`, `dictprep`, the `dictionaries` submodule) moved out, `internal/dict`
|
||||||
|
repointed at the committed `dawg/*.dawg` fixtures, tagged **v1.0.0**. scrabble-game pins it in
|
||||||
|
`backend/go.mod`, drops the `go.work` replace + the CI clone, and sets `GOPRIVATE=gitea.iliadenisov.ru/*`
|
||||||
|
(go fetches the module directly from Gitea — verified end-to-end). The solver hash lives in
|
||||||
|
`go.work.sum` (workspace mode; the bare-path `scrabble/pkg` replace still blocks `go mod tidy`).
|
||||||
|
- **TODO-2 — dictionary repo** (interview: "полный TODO-2, новый репо"): `developer/scrabble-dictionary`
|
||||||
|
builds the three DAWGs against the published solver + pinned `dafsa`/`alphabet` v1.1.0,
|
||||||
|
**byte-identical** to the solver fixtures; published as the release artifact
|
||||||
|
`scrabble-dawg-v1.0.0.tar.gz`; both Go workflows download it for `BACKEND_DICT_DIR` instead of
|
||||||
|
cloning the solver. English source vendored from `kamilmielnik/scrabble-dictionaries`; the Эрудит
|
||||||
|
fold is committed as `dictprep/russian/erudit.txt`, so the build needs no `python`.
|
||||||
|
- **Bootstrap nuances** (encountered): the dict repo was created empty with a protected `master`, so
|
||||||
|
it was seeded once via an owner-authorised protection lift→push→restore (a subsequent CI-fix push
|
||||||
|
correctly went through a PR, not another lift); it was made **public** (like the solver) so the Go
|
||||||
|
workflows fetch the artifact anonymously. Its CI is a **build-only** validation gate — the
|
||||||
|
auto-release step's `${{ github.* }}` contexts failed the Gitea workflow compile, so releases are
|
||||||
|
published manually for now (a logged follow-up).
|
||||||
|
|
||||||
## Deferred TODOs (cross-stage)
|
## Deferred TODOs (cross-stage)
|
||||||
|
|
||||||
- **TODO-1 — publish & version the solver.** Once `scrabble-solver` is stable,
|
- ~~**TODO-1 — publish & version the solver.**~~ **Done in Stage 14.** `scrabble-solver` is
|
||||||
give it a real module URL and switch `backend` to a versioned dependency,
|
published as module `gitea.iliadenisov.ru/developer/scrabble-solver` (tagged `v1.0.0`, with
|
||||||
dropping the `go.work` replace and the CI clone. Removes the floating
|
`wordlist`/`dictdawg` de-internalised to public packages); `backend/go.mod` pins it, the `go.work`
|
||||||
`master` dependency accepted for now (Stage 2 interview). **Planned for Stage 14**
|
replace and the CI sibling-clone are gone, and `GOPRIVATE=gitea.iliadenisov.ru/*` fetches it directly
|
||||||
(it cleans up the backend Docker build; a clone-in-build fallback stays available).
|
(no public proxy/checksum DB). Removes the floating `master` dependency accepted since Stage 2.
|
||||||
- **TODO-2 — split the solver into engine vs dictionary generator + versioned
|
- ~~**TODO-2 — split the solver into engine vs dictionary generator + versioned dictionary
|
||||||
dictionary artifacts.** Owner's idea, with the caveats agreed at the Stage 2
|
artifacts.**~~ **Done in Stage 14.** A new repo `developer/scrabble-dictionary` holds the word-list
|
||||||
interview: the split is sound (build-time wordlist→DAWG vs runtime load have
|
sources + `cmd/builddict` (moved out of the solver, with `dictprep` and the `dictionaries` submodule)
|
||||||
different lifecycles and shrink the runtime dependency surface), **but** the
|
and builds the three DAWGs against the **published solver + pinned `dafsa`/`alphabet` v1.1.0** — the
|
||||||
generator must pin the **same** `dafsa`/`alphabet` versions and alphabet
|
output is **byte-identical** to the solver's committed fixtures, so the index-drift caveat is handled
|
||||||
definitions as the runtime engine or the on-disk format / letter indexing
|
by construction. Delivered as a Gitea **release artifact** `scrabble-dawg-vX.Y.Z.tar.gz` (not
|
||||||
drifts and silently corrupts validation. For delivery prefer **Git LFS or an
|
`go get`; DAWGs are data; **one semver label for the whole set**); the Go workflows download it for
|
||||||
artifact store** (Gitea releases / OCI artifact / object storage) over a raw
|
`BACKEND_DICT_DIR`. The runtime dynamic-reload contract (per-version `BACKEND_DICT_DIR/<version>/` via
|
||||||
git submodule (the ~0.5–0.7 MB DAWGs are regenerated wholesale and bloat git
|
`Registry.LoadAvailable` / `engine.OpenWithVersions`, Stage 10) is unchanged — a deploy drops a new
|
||||||
history); pin by tag/hash for a reproducible startup set. A submodule/LFS pull
|
set into the directory; a version is safe to retire once no active game pins it.
|
||||||
is a **deploy-time** way to populate the directory, **not** the runtime
|
|
||||||
dynamic-reload mechanism (**implemented in Stage 10**: a per-version subdirectory
|
|
||||||
`BACKEND_DICT_DIR/<version>/` loaded via `Registry.LoadAvailable`, restart-restored by
|
|
||||||
`engine.OpenWithVersions`) — keep the `BACKEND_DICT_DIR` directory as
|
|
||||||
the runtime contract: a new `.dawg` appears in it and is loaded with
|
|
||||||
`dawg.Load`. **Planned for Stage 14**, agreed resolution: a **new versioned repo**
|
|
||||||
for the parsers + built DAWGs, delivered as a **release artifact** (not `go get`),
|
|
||||||
versioned with **one semver label for the whole set** (additive; old versions retired
|
|
||||||
once no active game pins them — see Stage 14). The generator must build against the same
|
|
||||||
`dafsa`/`alphabet`/solver as the runtime (the index-drift caveat, shared with TODO-4).
|
|
||||||
- ~~**TODO-3 — garbage-collect abandoned guest accounts.**~~ **Done in Stage 12.**
|
- ~~**TODO-3 — garbage-collect abandoned guest accounts.**~~ **Done in Stage 12.**
|
||||||
A periodic `account.GuestReaper` deletes guests (`is_guest`) **with no game seat at
|
A periodic `account.GuestReaper` deletes guests (`is_guest`) **with no game seat at
|
||||||
all** whose account age exceeds `BACKEND_GUEST_RETENTION` (default 30 d, swept every
|
all** whose account age exceeds `BACKEND_GUEST_RETENTION` (default 30 d, swept every
|
||||||
@@ -984,8 +1042,9 @@ dashboard stack; solver-publish vs clone-in-build; load expectations.
|
|||||||
produced from the solver ruleset (`engine.AlphabetTable`), so it is pinned by the solver
|
produced from the solver ruleset (`engine.AlphabetTable`), so it is pinned by the solver
|
||||||
version and cannot drift from the running backend, and `ui/src/lib/premiums.ts` is now
|
version and cannot drift from the running backend, and `ui/src/lib/premiums.ts` is now
|
||||||
geometry only. The durable journal / history / GCG stay decoded concrete characters (§9.1,
|
geometry only. The durable journal / history / GCG stay decoded concrete characters (§9.1,
|
||||||
unchanged). The DAWG/solver build-time agreement (the original caveat, shared with TODO-2)
|
unchanged). The DAWG/solver build-time agreement (the original caveat, shared with TODO-2) was
|
||||||
remains Stage 14.
|
discharged in Stage 14: the dict repo builds against the published solver + pinned
|
||||||
|
`dafsa`/`alphabet`, byte-identical to the fixtures.
|
||||||
- **TODO-5 — QR friend codes (owner's idea, Stage 8).** *Partially done in Stage 9:*
|
- **TODO-5 — QR friend codes (owner's idea, Stage 8).** *Partially done in Stage 9:*
|
||||||
the deep-link scheme now exists (`f<code>`, shared Go ↔ TS), the bot redeems it on
|
the deep-link scheme now exists (`f<code>`, shared Go ↔ TS), the bot redeems it on
|
||||||
launch, and the UI shows a **share-to-Telegram** link for an issued code when
|
launch, and the UI shows a **share-to-Telegram** link for an issued code when
|
||||||
|
|||||||
+21
-17
@@ -154,8 +154,11 @@ internal/connector/ # backend gRPC client to the Telegram connector (operator b
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker run -d --name scrabble-pg -e POSTGRES_PASSWORD=dev -p 5432:5432 postgres:17-alpine
|
docker run -d --name scrabble-pg -e POSTGRES_PASSWORD=dev -p 5432:5432 postgres:17-alpine
|
||||||
|
# DAWGs: extract the dictionary release artifact (or point at a local scrabble-solver/dawg):
|
||||||
|
mkdir -p /tmp/dawg && curl -fsSL https://gitea.iliadenisov.ru/developer/scrabble-dictionary/releases/download/v1.0.0/scrabble-dawg-v1.0.0.tar.gz | tar xz -C /tmp/dawg
|
||||||
BACKEND_POSTGRES_DSN='postgres://postgres:dev@localhost:5432/postgres?search_path=backend&sslmode=disable' \
|
BACKEND_POSTGRES_DSN='postgres://postgres:dev@localhost:5432/postgres?search_path=backend&sslmode=disable' \
|
||||||
BACKEND_DICT_DIR=../../scrabble-solver/dawg \
|
BACKEND_DICT_DIR=/tmp/dawg \
|
||||||
|
GOPRIVATE='gitea.iliadenisov.ru/*' \
|
||||||
go run ./cmd/backend
|
go run ./cmd/backend
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -178,19 +181,18 @@ go run ./cmd/jetgen # rewrites internal/postgres/jet against a temp containe
|
|||||||
|
|
||||||
## Engine & dictionaries
|
## Engine & dictionaries
|
||||||
|
|
||||||
`internal/engine` consumes the sibling `scrabble-solver` module in-process. Its
|
`internal/engine` consumes `scrabble-solver` in-process as a **published, versioned
|
||||||
bare module path (`scrabble-solver`, not a URL) cannot be fetched via VCS, so the
|
module** (`gitea.iliadenisov.ru/developer/scrabble-solver`, pinned in `go.mod`). Set
|
||||||
workspace `go.work` carries `replace scrabble-solver => ../scrabble-solver` and
|
`GOPRIVATE=gitea.iliadenisov.ru/*` so go fetches it directly from this Gitea (skipping
|
||||||
the build must run from the repository root (the workspace), not from this module
|
the public proxy/checksum DB); no sibling checkout or `go.work` replace is needed (for
|
||||||
in isolation. `github.com/iliadenisov/dafsa` (the DAWG loader) is a direct
|
local solver co-development you may add a temporary replace — see `go.work`).
|
||||||
dependency. CI clones the public solver repository into `../scrabble-solver`
|
`github.com/iliadenisov/dafsa` (the DAWG loader) is a direct dependency. The dictionaries
|
||||||
before building (see `.gitea/workflows/`); locally, check it out next to this
|
(`en_sowpods.dawg`, `ru_scrabble.dawg`, `ru_erudit.dawg`) ship as a **release artifact**
|
||||||
repository. Committed dictionaries (`en_sowpods.dawg`, `ru_scrabble.dawg`,
|
from the [`scrabble-dictionary`](https://gitea.iliadenisov.ru/developer/scrabble-dictionary)
|
||||||
`ru_erudit.dawg`) live in the solver's `dawg/` directory; the engine loads them
|
repo (one semver per set); the engine loads them by `(variant, dict_version)` from
|
||||||
by `(variant, dict_version)` from a directory path. Since Stage 3 the backend
|
`BACKEND_DICT_DIR`. Since Stage 3 the backend loads them at startup as a hard dependency
|
||||||
loads them at startup from the **required** `BACKEND_DICT_DIR` (a missing
|
(a missing dictionary aborts the boot). See [`../PLAN.md`](../PLAN.md) Stage 14
|
||||||
dictionary aborts the boot); the future versioned-artifact direction is recorded
|
(TODO-1/TODO-2).
|
||||||
in [`../PLAN.md`](../PLAN.md) TODO-2.
|
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
@@ -201,6 +203,8 @@ go test -tags=integration -count=1 -p=1 ./... # Postgres-backed (needs Docker)
|
|||||||
|
|
||||||
Integration tests are guarded by the `integration` build tag and run against a
|
Integration tests are guarded by the `integration` build tag and run against a
|
||||||
throwaway `postgres:17-alpine` container; they fail loudly when Docker is absent
|
throwaway `postgres:17-alpine` container; they fail loudly when Docker is absent
|
||||||
rather than skipping. The `internal/engine` tests load the committed DAWGs from
|
rather than skipping. The `internal/engine` tests load the DAWGs from
|
||||||
`BACKEND_DICT_DIR` (defaulting to the sibling `../scrabble-solver/dawg`) and fail
|
`BACKEND_DICT_DIR` (CI sets it to the extracted dictionary release artifact; locally it
|
||||||
loudly when that directory is absent.
|
defaults to a `scrabble-solver/dawg` sibling checkout) and fail loudly when that directory
|
||||||
|
is absent. `GOPRIVATE=gitea.iliadenisov.ru/*` is needed for go to fetch the pinned solver
|
||||||
|
module.
|
||||||
|
|||||||
+1
-1
@@ -3,6 +3,7 @@ module scrabble/backend
|
|||||||
go 1.26.3
|
go 1.26.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
gitea.iliadenisov.ru/developer/scrabble-solver v1.0.0
|
||||||
github.com/XSAM/otelsql v0.42.0
|
github.com/XSAM/otelsql v0.42.0
|
||||||
github.com/gin-gonic/gin v1.12.0
|
github.com/gin-gonic/gin v1.12.0
|
||||||
github.com/go-jet/jet/v2 v2.14.1
|
github.com/go-jet/jet/v2 v2.14.1
|
||||||
@@ -20,7 +21,6 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.43.0
|
go.opentelemetry.io/otel/sdk/metric v1.43.0
|
||||||
go.opentelemetry.io/otel/trace v1.43.0
|
go.opentelemetry.io/otel/trace v1.43.0
|
||||||
go.uber.org/zap v1.27.1
|
go.uber.org/zap v1.27.1
|
||||||
scrabble-solver v0.0.0-00010101000000-000000000000
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
"scrabble-solver/rules"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/rules"
|
||||||
)
|
)
|
||||||
|
|
||||||
// blankTile marks a blank tile in a hand or in the bag, matching the
|
// blankTile marks a blank tile in a hand or in the bag, matching the
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"scrabble-solver/rules"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/rules"
|
||||||
)
|
)
|
||||||
|
|
||||||
// allTiles returns the full multiset of tiles a bag is filled from, in ruleset
|
// allTiles returns the full multiset of tiles a bag is filled from, in ruleset
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"scrabble-solver/board"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/board"
|
||||||
"scrabble-solver/rules"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/rules"
|
||||||
"scrabble-solver/scrabble"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/scrabble"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ActionKind classifies a turn in the move log.
|
// ActionKind classifies a turn in the move log.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"scrabble-solver/scrabble"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/scrabble"
|
||||||
)
|
)
|
||||||
|
|
||||||
// blankCellFlag is the bit board cells set for a blank tile (board.go encoding).
|
// blankCellFlag is the bit board cells set for a blank tile (board.go encoding).
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"scrabble-solver/scrabble"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/scrabble"
|
||||||
)
|
)
|
||||||
|
|
||||||
// blankLetter is how a blank tile is written in the decoded, domain-facing API:
|
// blankLetter is how a blank tile is written in the decoded, domain-facing API:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"scrabble-solver/rules"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/rules"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Variant identifies a Scrabble variant the backend offers. Each maps to a
|
// Variant identifies a Scrabble variant the backend offers. Each maps to a
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"scrabble-solver/board"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/board"
|
||||||
"scrabble-solver/rack"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/rack"
|
||||||
"scrabble-solver/rules"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/rules"
|
||||||
"scrabble-solver/scrabble"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/scrabble"
|
||||||
)
|
)
|
||||||
|
|
||||||
// scorelessLimit is the number of consecutive scoreless turns (passes and
|
// scorelessLimit is the number of consecutive scoreless turns (passes and
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"scrabble-solver/board"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/board"
|
||||||
"scrabble-solver/scrabble"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/scrabble"
|
||||||
)
|
)
|
||||||
|
|
||||||
// newEnglishGame starts a two-player English game with the given seed.
|
// newEnglishGame starts a two-player English game with the given seed.
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"scrabble-solver/rules"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/rules"
|
||||||
"scrabble-solver/scrabble"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/scrabble"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testVersion labels the single dictionary version the tests register.
|
// testVersion labels the single dictionary version the tests register.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
dawg "github.com/iliadenisov/dafsa"
|
dawg "github.com/iliadenisov/dafsa"
|
||||||
|
|
||||||
"scrabble-solver/scrabble"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/scrabble"
|
||||||
)
|
)
|
||||||
|
|
||||||
// dictFiles maps each variant to its committed DAWG filename, as built by
|
// dictFiles maps each variant to its committed DAWG filename, as built by
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"scrabble-solver/board"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/board"
|
||||||
"scrabble-solver/scrabble"
|
"gitea.iliadenisov.ru/developer/scrabble-solver/scrabble"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestRegistryOpensEveryVariant checks that Open loads all three variants at the
|
// TestRegistryOpensEveryVariant checks that Open loads all three variants at the
|
||||||
|
|||||||
+14
-10
@@ -203,9 +203,10 @@ Key points:
|
|||||||
`Registry.LoadAvailable` (only the variants whose DAWG is present there), and a
|
`Registry.LoadAvailable` (only the variants whose DAWG is present there), and a
|
||||||
restart re-loads every resident version via `engine.OpenWithVersions` (the flat
|
restart re-loads every resident version via `engine.OpenWithVersions` (the flat
|
||||||
boot version plus each subdirectory). In-flight games keep their pinned version;
|
boot version plus each subdirectory). In-flight games keep their pinned version;
|
||||||
new games use the latest. (A future split of the solver into engine + dictionary
|
new games use the latest. (The solver is published as a versioned module and the
|
||||||
generator with versioned artifacts is recorded in [`../PLAN.md`](../PLAN.md)
|
dictionaries ship as a separate versioned **release artifact** from the
|
||||||
TODO-2.)
|
`scrabble-dictionary` repo — TODO-1/TODO-2, Stage 14; the runtime contract above is
|
||||||
|
unchanged.)
|
||||||
- Move generation/validation/scoring use `Solver.GenerateMoves` (ranked),
|
- Move generation/validation/scoring use `Solver.GenerateMoves` (ranked),
|
||||||
`Solver.ValidatePlay` and `Solver.ScorePlay`; board mutation uses
|
`Solver.ValidatePlay` and `Solver.ScorePlay`; board mutation uses
|
||||||
`scrabble.Apply`. The engine adds its own deterministic, seeded tile **bag**
|
`scrabble.Apply`. The engine adds its own deterministic, seeded tile **bag**
|
||||||
@@ -469,7 +470,7 @@ promotions) is future work and would deliver short markdown messages (text + lin
|
|||||||
`OTEL_EXPORTER_OTLP_*` environment) exports to a collector. The Postgres pool is
|
`OTEL_EXPORTER_OTLP_*` environment) exports to a collector. The Postgres pool is
|
||||||
instrumented with otelsql and `otelgrpc` traces the backend↔gateway push stream
|
instrumented with otelsql and `otelgrpc` traces the backend↔gateway push stream
|
||||||
and the gateway↔connector calls. The OTLP collector and Grafana dashboards are
|
and the gateway↔connector calls. The OTLP collector and Grafana dashboards are
|
||||||
stood up with the deploy (Stage 14).
|
stood up with the deploy (Stage 15).
|
||||||
- Per-request server-side timing via gin middleware from day one (the access log
|
- Per-request server-side timing via gin middleware from day one (the access log
|
||||||
carries method, route, status, latency and the active trace id). A
|
carries method, route, status, latency and the active trace id). A
|
||||||
client-measured RTT piggybacked on the next request is a later enhancement.
|
client-measured RTT piggybacked on the next request is a later enhancement.
|
||||||
@@ -516,7 +517,7 @@ a dedicated redeem sub-limit or a longer code is the hardening step if abuse app
|
|||||||
## 13. Deployment (informational)
|
## 13. Deployment (informational)
|
||||||
|
|
||||||
Single public origin, path-routed: a mini-landing at the root, the **Telegram Mini
|
Single public origin, path-routed: a mini-landing at the root, the **Telegram Mini
|
||||||
App under `/telegram/`** (the gateway serves the static UI build, wired in Stage 14;
|
App under `/telegram/`** (the gateway serves the static UI build, wired in Stage 15;
|
||||||
outside Telegram that path redirects to the root), the gateway public surface and the **admin console
|
outside Telegram that path redirects to the root), the gateway public surface and the **admin console
|
||||||
at `/_gm`** (backend-rendered, Basic-Auth at the gateway) share one host that
|
at `/_gm`** (backend-rendered, Basic-Auth at the gateway) share one host that
|
||||||
terminates TLS. The **Telegram connector** runs as a separate
|
terminates TLS. The **Telegram connector** runs as a separate
|
||||||
@@ -524,7 +525,7 @@ container with **no public ingress** — it long-polls Telegram and egresses thr
|
|||||||
VPN sidecar, answering only internal gRPC. MVP runs one `gateway`, one `backend`, one
|
VPN sidecar, answering only internal gRPC. MVP runs one `gateway`, one `backend`, one
|
||||||
Postgres, plus the connector. The connector's Docker/compose ships now
|
Postgres, plus the connector. The connector's Docker/compose ships now
|
||||||
(`platform/telegram/deploy`, mirroring `../15-puzzle`); the gateway's static UI serving
|
(`platform/telegram/deploy`, mirroring `../15-puzzle`); the gateway's static UI serving
|
||||||
and the full multi-service deploy land in Stage 14.
|
and the full multi-service deploy land in Stage 15.
|
||||||
|
|
||||||
## 14. CI & branches
|
## 14. CI & branches
|
||||||
|
|
||||||
@@ -536,9 +537,12 @@ and the full multi-service deploy land in Stage 14.
|
|||||||
`integration` build tag (testcontainers `postgres:17-alpine`, Ryuk disabled,
|
`integration` build tag (testcontainers `postgres:17-alpine`, Ryuk disabled,
|
||||||
serial). Further workflows (ui-test, deploy) are added with the components they
|
serial). Further workflows (ui-test, deploy) are added with the components they
|
||||||
cover.
|
cover.
|
||||||
- Since Stage 2 both Go workflows clone the public `scrabble-solver` sibling
|
- The engine consumes `scrabble-solver` as a **published, versioned module**
|
||||||
(master HEAD, no credentials) into `../scrabble-solver` before building, so the
|
(`gitea.iliadenisov.ru/developer/scrabble-solver`, pinned in `backend/go.mod`); both Go
|
||||||
`go.work` `replace` resolves; the engine tests read the committed DAWGs from
|
workflows set `GOPRIVATE=gitea.iliadenisov.ru/*` so go fetches it directly from this Gitea
|
||||||
that checkout via `BACKEND_DICT_DIR`.
|
(no public proxy/checksum DB, no sibling clone). The dictionaries ship as a **release
|
||||||
|
artifact** from the `scrabble-dictionary` repo; the workflows download
|
||||||
|
`scrabble-dawg-<DICT_VERSION>.tar.gz` and point the engine tests at it via
|
||||||
|
`BACKEND_DICT_DIR` (TODO-1/TODO-2 discharged in Stage 14).
|
||||||
- After any push, the run is watched to green before a stage is declared done
|
- After any push, the run is watched to green before a stage is declared done
|
||||||
(`python3 ~/.claude/bin/gitea-ci-watch.py`).
|
(`python3 ~/.claude/bin/gitea-ci-watch.py`).
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ use (
|
|||||||
./platform/telegram
|
./platform/telegram
|
||||||
)
|
)
|
||||||
|
|
||||||
// The scrabble-solver engine is consumed in-process as a library. Its module
|
// The scrabble-solver engine is consumed in-process as a published, versioned Gitea
|
||||||
// path is the bare "scrabble-solver" (not a URL), so it cannot be fetched as a
|
// module (gitea.iliadenisov.ru/developer/scrabble-solver, pinned in backend/go.mod). It
|
||||||
// versioned dependency via VCS; the workspace points it at the sibling checkout.
|
// is fetched directly from Gitea — set GOPRIVATE=gitea.iliadenisov.ru/* so go skips the
|
||||||
// CI clones that sibling next to this repository before building.
|
// public proxy/checksum DB. For local solver co-development, temporarily add:
|
||||||
replace scrabble-solver => ../scrabble-solver
|
// replace gitea.iliadenisov.ru/developer/scrabble-solver => ../scrabble-solver
|
||||||
|
|
||||||
// scrabble/pkg holds the shared wire contracts (push proto + FlatBuffers edge
|
// scrabble/pkg holds the shared wire contracts (push proto + FlatBuffers edge
|
||||||
// payloads) imported by both backend and gateway. Its module path has no dot, so
|
// payloads) imported by both backend and gateway. Its module path has no dot, so
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
|
gitea.iliadenisov.ru/developer/scrabble-solver v1.0.0 h1:ntN6m4cOB+4FelleO2nkAIZp8WSc+v25neetzfdUuuw=
|
||||||
|
gitea.iliadenisov.ru/developer/scrabble-solver v1.0.0/go.mod h1:G60OiGZtkrRyYX8P3SSsjVpU707fufmZkvCkNFPFWrY=
|
||||||
github.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw=
|
github.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw=
|
||||||
github.com/ClickHouse/clickhouse-go/v2 v2.45.0/go.mod h1:giJfUVlMkcfUEPVfRpt51zZaGEx9i17gCos8gBl392c=
|
github.com/ClickHouse/clickhouse-go/v2 v2.45.0/go.mod h1:giJfUVlMkcfUEPVfRpt51zZaGEx9i17gCos8gBl392c=
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||||
|
|||||||
Reference in New Issue
Block a user