diff --git a/AI_HANDOVER.md b/AI_HANDOVER.md index 0852248c..401cd514 100644 --- a/AI_HANDOVER.md +++ b/AI_HANDOVER.md @@ -55,7 +55,7 @@ Milestone progress spans foundational core engines through active production har ## Test Verification & CI - **Suite execution**: Run `npm test` across all root workspaces via `node --test`. -- **Live metrics**: `npm run test:counts` aggregates test and skip counts across package and service suites; consult [`docs/PROJECT_STATE.md`](docs/PROJECT_STATE.md) for current tooling caveats and measured validation status. +- **Live metrics**: After the host setup below, `npm run test:counts` aggregates test and skip counts across root workspace packages and the standalone gateway service. Skipped integration tests are not passes; consult [`docs/PROJECT_STATE.md`](docs/PROJECT_STATE.md) for measured validation status and active defects. - **Hermetic defaults**: Unit and domain suites run offline without external infrastructure. Integration suites gate on environment variables: `DATABASE_URL` (PostgreSQL persistence), `REDIS_URL` (gateway Redis integration and scaling), and provider API keys (live AI features). - **Continuous Integration (`.github/workflows`)**: - Multi-version Node matrix (Node 22 / 24) with strict typechecking, linting, and ADR claim validation (`npm run check:adr-claims`). @@ -71,14 +71,16 @@ Milestone progress spans foundational core engines through active production har ## Build & Run ```bash -npm ci # reproducible install (root package-lock.json is committed) -npm run build # builds all root workspace packages in dependency order -npm test # executes workspace package test suites via node --test -npm run lint # strict typecheck across all root workspaces -npm run test:counts # aggregates test and skip counts across suites (see PROJECT_STATE caveats) +npm ci # install root workspace dependencies from the root lockfile +npm run build # build root workspace packages in dependency order +npm ci --prefix services/gateway # install the standalone service from its own lockfile +npm test # execute root workspace package test suites +npm run lint # strict typecheck across root workspaces +npm run test:counts # execute and count workspace + gateway service tests ``` - **Build order**: Run **`npm run build` before `lint` or `test` on a fresh clone** — downstream packages resolve upstream types from built `dist/`. +- **Standalone gateway**: Root `npm ci` and `npm run build` exclude `services/gateway`. Its separate install is required before `test:counts` or direct gateway tests, even without `REDIS_URL`. The test command compiles its own `dist-test/`; a gateway production build is only needed for its `dist/` output. See [`docs/RUNNING.md`](docs/RUNNING.md#host-build-tests-and-live-counts) for host setup and troubleshooting. - **Local full stack**: `docker compose up --build` (see [`docs/RUNNING.md`](docs/RUNNING.md)). - **Helm chart validation**: `bash scripts/helm-snapshot-test.sh` (or lint with test secrets: `helm lint deploy/helm/gambit --set secrets.accessTokenSecret=test-only-access-token-secret-32-bytes-minimum --set secrets.postgresPassword=test-only-postgres-password --set config.nodeEnv=development --set email.provider=console`). diff --git a/README.md b/README.md index d8c6f2b8..45ccddd6 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,10 @@ chess-platform/ production adapters (Postgres event log, Redis pub/sub, `ws`). Deployment assets live under `deploy/`; see the roadmap for remaining infrastructure work. +For a fresh clone, follow the [host build and test setup](docs/RUNNING.md#host-build-tests-and-live-counts). +It covers the root workspace build and the separate gateway dependency install required by +`npm run test:counts`. + ## `@chess-platform/core` A dependency-free, fully-typed chess engine. diff --git a/docs/CI_SETUP.md b/docs/CI_SETUP.md index da63a77a..bcca12dd 100644 --- a/docs/CI_SETUP.md +++ b/docs/CI_SETUP.md @@ -65,8 +65,12 @@ ADR-0121 is additionally guarded in seconds by ## Notes -- Installs use `npm ci` against the committed root `package-lock.json` for - reproducible builds. +- Root workspace installs use `npm ci` against the committed root `package-lock.json`. + The `gateway-service` job then builds the root packages and runs a second `npm ci` + in `services/gateway`, using that service's own committed `package-lock.json`, + before building, typechecking, and testing the service. Root install/build commands + exclude the standalone service. Host preparation for these checks is documented in + [RUNNING.md](RUNNING.md#host-build-tests-and-live-counts). - The deeper chart-wiring checks (env-var ordering, gateway `replicas: 2`, secret sourcing, the search kill switches, and search indexer isolation) live in `scripts/helm-snapshot-test.sh`. The default of 2 gateway replicas enables diff --git a/docs/PROJECT_STATE.md b/docs/PROJECT_STATE.md index 40d24840..170017da 100644 --- a/docs/PROJECT_STATE.md +++ b/docs/PROJECT_STATE.md @@ -4,7 +4,78 @@ > to read **only this file** and continue immediately. Updated after every > milestone and every significant architectural step. -_Last updated: 2026-09-05 — M15 Increment 49: the durable analysis-cache suite establishes its own database._ +_Last updated: 2026-09-05 — M15 Increment 50: test:counts / standalone gateway host setup contract._ + +## M15 Increment 50 — test:counts / standalone gateway host setup contract + +**Status: RESOLVED — SETUP / DOCUMENTATION CONTRACT DRIFT CORRECTED.** The bounded +gateway setup investigation recorded in Increments 48 and 49 is closed. The gateway +intentionally remains outside the root `packages/*` workspaces. No gateway implementation +defect was proven, and no workspace topology, script, lockfile, CI, Docker or runtime change +is required. Increment 49's durable analysis-cache database-ownership defect remains resolved. + +**Supported host preparation, from the repository root with Node.js 22+:** + +```sh +npm ci +npm run build +npm ci --prefix services/gateway +npm run test:counts +``` + +Root `npm ci` installs the root workspaces, but not the standalone gateway dependencies. +Root build establishes the workspace public `dist` outputs used by the gateway's local +`file:` dependencies; installing those links does not build their targets. The gateway has +its own manifest, lockfile and install lifecycle. `test:counts` includes the 19 root workspace +suites and the standalone gateway suite, but neither installs dependencies nor establishes +the workspace public outputs. Gateway tests compile `dist-test`; a prior gateway production +build is not required. The sequence above follows the existing CI preparation order. + +**Controlled evidence on historical main `771b1f93c05585294474e95fcb24bf116766db3d`.** +Four independent archives began without dependency trees or build outputs. Root install +alone (A) failed with missing workspace outputs and gateway dependencies. Root install plus +root build (B) left only the gateway failing, with six `TS2307` diagnostics for `ioredis`. +Root plus gateway installs without root build (C) resolved `ioredis`, but failed on missing +workspace public outputs. Full preparation (D) made the gateway pass: 16 tests, 11 passed, +5 skipped. D's aggregate still exited 1 because `openapi.test.js` produced a bare file-level +`test failed`; its isolated rerun passed all 18 tests. The failure did not reproduce in that +isolated run, and the observation does not identify its mechanism. + +Adding only the gateway install to B made its direct test pass, with no root rebuild or +gateway production build. Repeating root `npm ci` then preserved both the gateway dependency +tree and existing workspace outputs, and the direct gateway test still passed. A root clean +install in an already prepared checkout is therefore not a wholly clean repository state. +This explains the setup-dependent exit-1 versus exit-0 observations: host preparation and +documentation had drifted, rather than the gateway needing to become a root workspace. +The exact sequence on the original historical machine cannot be recovered. An independent +prepared acceptance tree subsequently measured `test:counts` exit 0, 3269 total, 115 skipped; +those numbers predate PR #43 and are historical, not current counts. + +**Documentation correction.** `AI_HANDOVER.md`, `README.md`, `docs/RUNNING.md` and +`docs/CI_SETUP.md` now state or link the complete host setup. This entry and `docs/ROADMAP.md` +synchronize the canonical record after PR #43, without changing the counting implementation. +Both lockfiles were unchanged throughout the controlled investigation. + +**Current measurement after synchronizing PR #43's main +`026005b2420006e04028bff4c69a16f30c78a905`, on 2026-09-05.** On Windows with Node +24.15.0 and npm 11.12.1, root `npm ci`, root build and gateway `npm ci` each exited 0. +`npm run test:counts` then exited **0: 3272 tests, 118 skipped**. The root subtotal was +**3256 tests, 113 skipped** across 19 workspaces; gateway was **16 tests, 11 passed, +5 skipped**. Root `npm test` also exited 0. This remeasurement used the task's previously +prepared checkout with both installs repeated; the independent clean-state proof is above. +`DATABASE_URL` and `REDIS_URL` were unset. Redis-backed tests were **NOT RUN**; skipped +database/Redis coverage is not a passing integration result. These current counts supersede +the historical 3269/115 measurement for this revision only. + +**Signature B remains UNRESOLVED and under separate investigation.** The D observation above +is retained independently of the resolved setup defect. No mechanism or unmerged findings +from the separate diagnostic PR are adopted here. Successful later runs do not resolve it. +During the post-review verification repeat, `learning-api.test.js` also produced a bare +file-level `test failed`: API reported 975 tests, 927 passed, 1 failed and 47 skipped. +Its isolated rerun passed all 22 tests. This is another observation of the failure shape, +not proof of a mechanism or a gateway setup failure; the earlier successful aggregate +measurement above is retained as a separate run. +Environment-gated skips are not passes; Redis-backed tests require a separate Redis-enabled run. ## M15 Increment 49 — the durable analysis-cache suite establishes its own database diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 57abfdaa..f8c030d1 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1354,7 +1354,7 @@ Debt observed during M14. Each states what is known, not what is planned; items - **The persistence integration suite was not idempotent against a reused database (RESOLVED in M15 Increment 46).** Recorded as a known defect by Increment 45 and left open there. Against a fresh PostgreSQL 16 database the suite passed; a second run against the *same* database failed nine tests, deterministically — measured on 16.14 before any edit as **173 pass / 0 fail** then **164 pass / 9 fail**. CI provisions a fresh server per run, so it never surfaced there. One contract was being broken in two directions: a suite sharing `chess_test` must remove every row it created and remove nothing else. `achievements.integration.test.ts` broke the second half with an unqualified `DELETE FROM users` in `beforeEach` — `games.white_id` and `games.black_id` are the only references to `users` without `ON DELETE CASCADE` (thirty-one FKs point at `users`; twenty-nine cascade; the two that do not are both on `games`), so one game left behind by `pg.integration.test.ts` aborted the wipe with SQLSTATE 23503 before any assertion ran, and the same statement destroyed the bot accounts migration 0021 seeds, which nothing restores because `migrate` has already recorded 0021 as applied. `pg/identity-tokens.test.ts` and `tournaments.pg.integration.test.ts` broke the first half, leaving fixed primary keys behind and colliding on `users_pkey` and `tournaments_pkey` (the latter surfacing through the repository's compare-and-set as `VersionConflictError`). **Resolved in Increment 46:** suites that legitimately share the database delete exactly their own rows through `withSharedDatabase` (`packages/persistence/src/test-support/fixtures.ts`), the sibling of Increment 45's `withTestDatabase` and heir to its precedence rule — a cleanup failure never replaces the assertion that actually failed, and never disappears either. `pg.integration.test.ts` moved to disposable databases instead, because it cannot meet the cleanup half at all: it appends to `game_events`, which is append-only by production trigger, so cleaning up after itself would have meant weakening a production safety rule to suit a test. `users-batch`, `anti-cheat`, `bot-reports` and `analysis-cache` were corrected for the same contract though none of them ever failed — fresh `uuidv7()` ids meant their leaked rows could not collide, so the tables merely grew on every run. Serialization was not the fix and was not introduced: `--test-concurrency=1` is a pre-existing documented invariant and the second run failed identically under it. Acceptance is three consecutive runs against one database with no reset between them (186/186/186), after which only the three migration-seeded bot accounts remain, with no leaked disposable databases and no lingering backends; falsification killed 17 of 20 mutations. No production code, migration, checksum, constraint or repository conflict semantic changed, and no migration was added. - **The API pg-security integration suite leaked every row it created into the shared database (RESOLVED in M15 Increment 48).** Recorded as a known defect by Increment 46 and deliberately left open there. `packages/api/test/pg-security.integration.test.ts` created users, password credentials, roles, sessions and rate-limit buckets through real repositories and closed its pools without removing any of them. Because every identifier it mints is a fresh `uuidv7()`, no run collided with another, so all 11 tests passed indefinitely while the database grew — measured on PostgreSQL 16.14 before any edit as **11/11 passing and 25 rows leaked on the first run (4 users, 4 credentials, 4 roles, 4 sessions, 9 rate-limit buckets), then 11/11 again and an identical further 25 on a second run against the same database**. The failure mode was silent accumulation, not a failing test, which is why nothing in the file could see it. `rate_limit_buckets` is the sharpest case: no foreign key references it, so no cascade can ever reach those rows, and `PgRateLimiter.sweep` only evicts buckets that expired over an hour ago. **Resolved in Increment 48:** every test runs inside Increment 46's `withSharedDatabase` contract and names what it owns — users deleted by exact owned id, with the proven `ON DELETE CASCADE` foreign keys removing their credentials, roles and sessions (the only non-cascading references to `users` are `games.white_id` and `games.black_id`, and this file creates no games), and rate-limit buckets deleted by exact owned key. Identifiers are recorded before the statement that creates the row, so a body that throws after a commit still surrenders it. Prefix-matched cleanup, `TRUNCATE`, broad `DELETE`, random-identifier workarounds, retries, conflict suppression and serialization were all rejected; `./test-support/fixtures` was added to the persistence package's `exports` so the canonical helper could be reused rather than duplicated. A second defect was fixed in the same increment: the bucket-creation race test read backend PIDs before the `try` whose `finally` releases the leased client, and a pool with a client still checked out never settles `pool.end()`, so a failure there hung the file instead of reporting the error — both reads now sit inside the protected region. Acceptance is three consecutive runs against one migrated database with no reset between them (**11/11, 11/11, 11/11**), after which the database state is identical to its pre-run reading, migration seeds and unrelated sentinels are preserved, no `test_db_*` databases are leaked and no backends linger; falsification killed **8 of 9 mutations**, the survivor being the `backendPid` error path, which needs fault injection no passing suite performs. No production code, migration, constraint, foreign key or repository semantic changed. - **`analysis-cache-durable.integration.test.ts` depended on schema it did not establish (RESOLVED in M15 Increment 49).** Recorded as a known defect by Increment 48 and deliberately left open there. `packages/api/test/analysis-cache-durable.integration.test.ts` never called `migrate()`: it opened pools straight onto `DATABASE_URL` and assumed `engine_analysis_cache` — created by migration `0026` and indexed by `0027` — was already there. Re-proven on current `main` before any edit, on PostgreSQL 16.14: against a genuinely fresh, never-migrated database it was **10 tests, 4 pass, 6 fail**, three of them throwing SQLSTATE **42P01** from the suite's own `LOCK TABLE`, `DELETE` and `UPDATE`, and three failing as assertions because the durable row they expected was never written. The four that passed did so vacuously: `PgAnalysisCache` absorbs a database fault and returns a miss, so a suite about durability ran with no durability at all. Against an already-migrated database it was **10/10 — and left 5 rows behind every run**, `freshFen()` being collision-avoidance rather than cleanup. The masking was measured, not assumed: the whole `packages/api` package on a fresh database gave the same **6 failures**, while running `packages/persistence` first (186 pass) and then the file gave **10/10** — seventeen persistence suites migrate the shared `DATABASE_URL`, and both the root `test` script and the CI `postgres-integration` job run that package first. **Resolved in Increment 49:** the suite applies the canonical migrations itself, once per file behind a flag, asking the persistence package for the directory it ships (`migrationsDir()`) instead of assembling one from `process.cwd()`; every test runs inside Increment 46's `withSharedDatabase` contract; each FEN is recorded before the statement that creates its row; and cleanup deletes by exact FEN equality, never by the placement prefix every standard starting position shares. A disposable database per test was considered and rejected on cost and orphan risk; the shape chosen is the one the sibling suite for the same table already uses. Acceptance is three independent brand-new empty databases (**10/10, 10/10, 10/10**, 0 rows of residue each), an already-migrated database whose five unrelated rows survive untouched, and the whole API package on a brand-new database with no other package first (**996 tests, 986 pass, 0 fail, 10 skipped**, 0 residue, 0 leaked `test_db_*`). Falsification killed **7 of 9 mutations**; the two survivors are a failure-precedence contract owned by `withSharedDatabase` and killed by its own suite, and an equivalent mutant. No production code, migration or repository semantic changed. -- **`npm run test:counts` exits non-zero because `services/gateway` sits outside the npm workspaces (OPEN, observed during M15 Increment 48).** During Increment 48 the script **exited 1**, reporting **3286 tests (28 skipped)** at the implementation HEAD and **3301 tests (28 skipped)** after that branch was synchronized with `main` — the exit code and its cause unchanged by the merge. The non-zero exit came entirely from `services/gateway`: the root `workspaces` field is `packages/*`, so that service's dependencies are never installed by a root `npm install` and its local `ioredis` module resolution fails during the counts run. No gateway dependency was installed or mutated while closing Increment 48, because that would modify `services/gateway/package-lock.json` outside the increment's scope. **M15 Increment 49 measured it again on its own branch and it is unchanged in kind: exit 1, 3256 tests (113 skipped), `gateway-service: ERROR` with six `TS2307: Cannot find module 'ioredis'` diagnostics.** **Open:** a bounded tooling/workspace investigation in its own right. The remedy is not assumed to be "add it to the workspaces" — what the service's exclusion is for, and what CI relies on, has to be established before changing it. +- **M15 Increment 50 — test:counts / standalone gateway host setup contract (RESOLVED: SETUP / DOCUMENTATION CONTRACT DRIFT CORRECTED).** Increments 48 and 49 recorded gateway `ioredis` compilation failures during counts runs. Controlled clean states proved that root `npm ci` does not install the intentionally standalone gateway, and gateway installation alone does not build the public workspace outputs its local `file:` dependencies need. The supported host sequence, from the root with Node.js 22+, is `npm ci`, `npm run build`, `npm ci --prefix services/gateway`, then `npm run test:counts`. The counting command includes the gateway but does not install dependencies or build workspace public outputs. A root clean install preserves an already prepared gateway dependency tree and workspace outputs, explaining why prepared and unprepared hosts gave different results. No gateway implementation defect was proven; no workspace topology, script, lockfile, CI, Docker or runtime change is required. Setup instructions and the canonical handover are now synchronized; see [M15 Increment 50](PROJECT_STATE.md#m15-increment-50--testcounts--standalone-gateway-host-setup-contract) for controlled evidence and validation. **Signature B remains unresolved and under separate investigation:** historical state D passed the gateway but failed the aggregate with a bare file-level `test failed` in `openapi.test.js`; the isolated rerun passed. No mechanism is assigned to that observation and no unmerged diagnostic findings are adopted. Skips are not passes. - **`ApiServer.listen` registered no `'error'` handler, so a failed bind hung and raised an uncaught event (RESOLVED in ADR-0140 §5).** `packages/api/src/server.ts` resolved its promise from the `listening` callback only and built it with no reject path. A bind failing asynchronously (`EADDRINUSE`, `EMFILE`) left the promise pending forever and, with no `'error'` listener on the `http.Server`, was re-raised as an uncaught exception. Found while investigating ADR-0140 and independently raised by the Qodo review of PR #21. **Resolved in the same increment** rather than deferred, because ADR-0140 §2's bounded, diagnosable acquisition is not true without it — the retry can only report a bind error if the listener it is handed rejects. A one-shot `'error'` listener now rejects and is removed once listening, so later server errors keep their previous semantics rather than being swallowed by a `reject` on a settled promise. The regression test fails against the exact pre-fix code, through an uncaught `ERR_UNHANDLED_ERROR`. - **`main.ts`'s controller-disposal list is manual, untested, and silently incomplete when a section is added (RESOLVED in Increment 25 / ADR-0092).** `run()` in `packages/web/src/main.ts` disposes the previous route's controllers by name, and its own comment says doing so "is what makes re-bootstrapping safe" — but adding a section to `bootstrap` and forgetting to add it there compiles, passes every gate, and leaks. Increment 23 shipped exactly that omission for `LearningController` and it was caught in PR review, not by a test. `main.ts` has no test coverage of any kind, so no section's disposal is verified. A structural fix (bootstrap returning its disposables as a collection, or a type-level exhaustiveness check keyed off the result type) would make the next omission a compile error; it is a refactor across ~15 return sites and belongs in its own increment. **Resolved in Increment 25 (ADR-0092):** extracted `createLifecycle` run loop in `lifecycle.ts`, defined `BootstrappedDisposables` and `DisposableKey` driving `DISPOSABLE_TEARDOWN_MAP: Record` for compile-time exhaustiveness, normalised `.dispose()` verb across all disposables, and cascaded `GameController.stop()` to `gameSync.stop()`. - **`stepView` sends the answers to the learner (RESOLVED in Increment 29 / ADR-0095).** `packages/api/src/presenters.ts` emits `expectedSan` on a move step and `correctIndex` on a quiz step, and `GET /v1/lessons/:id/steps` is the route the learner's own lesson page calls. Increment 23 omits both from the client-side types (`packages/web/src/api/models.ts`), so the app cannot render or grade against them and a future edit that tries becomes a compile error — but the fields are still on the wire and readable in devtools. The authoring routes legitimately need them returned to the author, so the fix is a learner-scoped step view (or a caller-dependent projection), not a deletion: an API contract decision with its own ADR. Nothing rated or rewarded depends on step progress today, so this is a wart rather than a breach. **Resolved in Increment 29 (ADR-0095):** added `LearnerStepView` / `learnerStepView` in `packages/api/src/presenters.ts` omitting `expectedSan` and `correctIndex`. `GET /v1/lessons/:id/steps` and `GET /v1/steps/:id` now check course authorship via `repo.getLesson` / `repo.getCourse`, returning full `stepView` to the author and `learnerStepView` to learners and anonymous callers. Updated OpenAPI schema and web model comments. The first attempt resolved authorship with a separate `getLesson` + `getCourse` after the step read, which doubled both routes from 3 SQL queries to 6 because `listSteps` / `getStep` had already made those reads internally and discarded the course; caught in the PR #92 review and fixed by adding `getStepWithCourse` / `listStepsWithCourse` to `LearningRepository`, which return what was already loaded. Both routes now make exactly one repository call, pinned by a counting-proxy test. diff --git a/docs/RUNNING.md b/docs/RUNNING.md index bcae2569..636dd967 100644 --- a/docs/RUNNING.md +++ b/docs/RUNNING.md @@ -8,9 +8,9 @@ Postgres, a real API, a real WebSocket gateway, and the web frontend. - [Docker](https://docs.docker.com/get-docker/) with the Compose plugin (v2+) - Postgres and nginx run inside containers — no local install needed -- [Node.js](https://nodejs.org/) 22+ on the host, only if you want to run - `scripts/smoke-test.mjs` outside a container (it uses the built-in `fetch` - and `WebSocket` globals) +- [Node.js](https://nodejs.org/) 22+ on the host for dependency installation, + builds, tests (including `test:counts`), or `scripts/smoke-test.mjs` outside + containers. The smoke script uses the built-in `fetch` and `WebSocket` globals. ## Quick start @@ -183,6 +183,48 @@ docker compose down # stop containers docker compose down -v # stop + delete the Postgres data volume ``` +## Host build, tests and live counts + +For tests outside Docker, use Node.js 22+ and run these commands **from the repository root**: + +```bash +npm ci # root workspace dependencies, from package-lock.json +npm run build # public packages/*/dist outputs, in dependency order +npm ci --prefix services/gateway # standalone dependencies, from its own package-lock.json +npm run test:counts # execute and count workspace + gateway service tests +``` + +Root workspaces are `packages/*`. Root `npm ci` does not install `services/gateway`, +and root `npm run build` does not compile it. The service deliberately has its own +dependency tree and lockfile; its `file:../../packages/...` dependencies link to local +workspace packages, whose public JavaScript and type entries point at `dist/`. +Installing those links does not build their targets. Workspace tests compile into +`dist-test/`, which does not replace the public `dist/` build. + +`test:counts` runs the workspace tests and `npm test --prefix services/gateway`. +It does not install dependencies or run production builds. For separate checks after +the setup above: + +```bash +npm test # root workspace tests +npm run lint # root workspace typecheck +npm test --prefix services/gateway # compiles and tests gateway dist-test/ +npm run build --prefix services/gateway # compiles gateway dist/ for npm start +``` + +A gateway production build is not a prerequisite for its test command. Redis-backed +tests skip when `REDIS_URL` is unset, but their static `ioredis` imports still require +the gateway dependencies at compilation and module-loading time. Skips are reported +separately and do not prove the Redis integration. With a test Redis instance available +at `REDIS_URL`, `npm run test:gateway-redis` runs the gateway build and test suite. + +If `gateway-service: ERROR` includes `Cannot find module 'ioredis'`, run the separate +gateway install above. Missing `@chess-platform/...` modules or type declarations on +a fresh checkout require the root install and build as well. A root build alone cannot +supply `ioredis`. Rerunning root `npm ci` in a prepared checkout retains gateway +dependencies and existing build outputs, so that is not equivalent to a fresh clone. +Docker prepares dependencies inside its images; it does not prepare host `node_modules`. + ## Running the CI checks locally `npm run ci:local` runs what `.github/workflows/ci.yml` runs — build, typecheck, test, and the @@ -191,6 +233,10 @@ minutes quota, a fork without Actions, no network), and `npm run check:ci-parity if the runner and the workflow ever disagree, so it stays a preview of CI rather than a second suite of its own. +Complete the [host setup above](#host-build-tests-and-live-counts) first. The local +runner executes builds and checks, but assumes dependencies are already installed; +it performs neither the root install nor the separate gateway install. + ```bash npm run ci:local --quick # everything that needs no services ```