Files
galaxy-game/ui/core
Ilia Denisov 8565942392
Build · Site / build (push) Successful in 8s
Tests · Go / test (push) Successful in 2m22s
Tests · UI / test (push) Failing after 2m42s
feat(deploy): single-origin path-based deployment + project site
Serve the whole stack behind one host: site at /, game UI at /game/,
gateway REST at /api + /healthz, Connect at /rpc (prefix stripped by the
edge Caddy). The built artifact is domain-agnostic — the UI talks to the
gateway same-origin via relative URLs, so the same bundle runs under any
host with no rebuild and with CORS disabled.

- Rename the Connect proto service galaxy.gateway.v1.EdgeGateway ->
  edge.v1.Gateway; regenerate Go + TS; public path /rpc/edge.v1.Gateway.
- Move the game UI under base path /game (env BASE_PATH); make the
  manifest, service-worker scope, WASM loader, and all navigation
  base-aware via a withBase helper.
- Relative API + /rpc Connect prefix; Vite dev proxy mirrors the strip.
- Rewrite the edge Caddy (dev + prod) for path-based routing; empty CORS
  allow-lists (same-origin); single host.
- New VitePress project site (site/): i18n en/ru with switcher, LaTeX
  math, minimal monospace theme; built and served at /.
- dev-deploy compose/Makefile + CI (dev-deploy, prod-build, new
  site-build) build and seed the site; probes hit /, /game/, /healthz.
- Sync docs (ARCHITECTURE, gateway README/openapi, dev-deploy &
  local-dev READMEs, CLAUDE.md, ui/PLAN).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 18:19:07 +02:00
..
2026-05-07 09:40:37 +02:00
2026-05-07 09:40:37 +02:00
2026-05-07 09:40:37 +02:00

ui/core — Galaxy Client Compute Module

ui/core (Go module galaxy/core) is the compute boundary of the Galaxy cross-platform UI client. It carries v1 transport-envelope canonical bytes, signature verification, and Ed25519 keypair helpers. Network I/O and persistent storage live elsewhere on purpose: this module compiles unchanged to WASM (the web target today) and is designed to also compile to gomobile (mobile) and Wails-embedded native (desktop) — both planned, see ../ROADMAP.md.

The authoritative byte contract is defined in docs/ARCHITECTURE.md §15. The gateway mirrors this exact wire format in its own gateway/authn package; cross-module byte parity and round-trip sign/verify are exercised by gateway/authn/parity_with_ui_core_test.go.

Invariants

  • No network. No net/http, no net/url, no gRPC client.
  • No storage. No os (outside _test.go fixtures), no SQL, no filesystem, no keychain.
  • TinyGo-friendly. Production files do not import crypto/x509, encoding/pem, or any package not supported by the WASM target. PKCS#8 PEM is server-only and stays in gateway/authn.
  • No goroutines, no channels, no sync primitives in production files. Pure functions, deterministic outputs.
  • No re-export of crypto/ed25519 types in the public API. Callers see opaque []byte blobs and string representations so the WASM bridge can hand them across the JS boundary as Uint8Array and string primitives.
  • Randomness is injected. keypair.Generate(reader io.Reader) takes a caller-supplied reader. Production code passes crypto/rand.Reader; tests use deterministic bytes.NewReader; WASM later passes a crypto.getRandomValues adapter.

Layout

ui/core/
├── go.mod               module galaxy/core (Go 1.26.0)
├── calc/                thin wrappers over `pkg/calc/` (no math here)
│   ├── ship.go          ship geometry, mass, speed, combat
│   ├── planet.go        ship build cost / per-turn production
│   ├── solve.go         goal-seek inverse solvers
│   └── number.go        display rounding (Ceil3)
├── canon/               canonical-bytes builders and verifiers
│   ├── canon.go         length-prefix helpers
│   ├── request.go       galaxy-request-v1 fields and signing input
│   ├── response.go      galaxy-response-v1 fields and verifier
│   ├── event.go         galaxy-event-v1 fields and verifier
│   ├── signature.go     base64 client-key request verification
│   └── testdata/        committed JSON golden vectors
├── keypair/             Ed25519 generate / sign / verify / marshal
└── types/               full transport envelopes + result codes

Public API

galaxy/core/canon

  • RequestDomainMarkerV1, ResponseDomainMarkerV1, EventDomainMarkerV1 — UTF-8 domain prefixes that bind a signature to a specific envelope kind.
  • RequestSigningFields, ResponseSigningFields, EventSigningFields — exact subsets of envelope fields covered by the v1 signature.
  • BuildRequestSigningInput, BuildResponseSigningInput, BuildEventSigningInput — produce canonical bytes ready for ed25519.Sign / ed25519.Verify.
  • VerifyRequestSignature(clientPublicKey string, signature []byte, fields RequestSigningFields) error — accepts the base64 string form the backend stores in the device session.
  • VerifyResponseSignature(publicKey ed25519.PublicKey, signature []byte, fields ResponseSigningFields) error, VerifyEventSignature(publicKey ed25519.PublicKey, signature []byte, fields EventSigningFields) error — used by the client to validate server output.
  • VerifyPayloadHash(payloadBytes, payloadHash []byte) error.
  • Sentinel errors: ErrInvalidPayloadHash, ErrPayloadHashMismatch, ErrInvalidClientPublicKey, ErrInvalidRequestSignature, ErrInvalidResponseSignature, ErrInvalidEventSignature.

galaxy/core/keypair

  • Generate(reader io.Reader) (privateKey, publicKey []byte, err error).
  • Sign(privateKey, message []byte) ([]byte, error) — returns a 64-byte raw Ed25519 signature.
  • Verify(publicKey, message, signature []byte) bool.
  • MarshalPublicKey(publicKey []byte) (string, error) — base64 StdEncoding, the wire format documented in §15.
  • UnmarshalPublicKey(value string) ([]byte, error).
  • PublicKeyFromPrivate(privateKey []byte) ([]byte, error).
  • Sentinel errors: ErrInvalidPrivateKey, ErrInvalidPublicKey, ErrInvalidPublicKeyEncoding.

galaxy/core/calc

Thin Go bridges over pkg/calc/, surfaced via WASM to the ship-class calculator and the ship-group inspector. Each function is a one-line passthrough — no math lives here. Coverage spans ship geometry / mass / speed / cargo, combat (attack, defence, bombing), block-upgrade and per-turn ship-build cost, the single-target goal-seek inverse solvers, and display rounding (Ceil3).

The authoritative function surface (which UI feature uses what, parity rules, what is still deferred) lives in ui/docs/calc-bridge.md.

galaxy/core/types

  • RequestEnvelope, ResponseEnvelope, EventEnvelope — full Go envelope structs mirroring the protobuf messages in gateway/proto/galaxy/gateway/v1/. Each exposes a SigningFields() method to project onto the corresponding canon.*SigningFields.
  • ProtocolVersionV1 = "v1", ResultCodeOK = "ok" — the only result string that is part of the stable client contract; any other result_code is downstream-opaque and must not be hard-coded by clients.

Testing

go test -count=1 ./ui/core/...

The canon test suite combines:

  • byte-equality on golden JSON fixtures under canon/testdata/ for three request types (user.account.get, lobby.my.games.list, user.games.command), one response (ok), and one event (gateway.server_time);
  • mutation tests proving every signed field is bound into the signature;
  • round-trip sign-then-verify across all three envelope kinds;
  • negative tests for tampered hashes, mismatched timestamps and request IDs, invalid signature lengths, and bad public-key encodings.

Cross-module parity (gateway accepts ui/core signatures and vice versa) is enforced from gateway/authn/parity_with_ui_core_test.go.

What this module is not

  • Not a network client. ConnectRPC over @connectrpc/connect-web on the TypeScript side is the only network surface.
  • Not a key store. Per-platform secure storage lives in the platform KeyStore / Cache layer on the TypeScript side.
  • Not a freshness gate. Server-side ±5 min freshness checks remain in gateway/internal/grpcapi/freshness_replay.go. The client is expected to stamp its own timestamp_ms accurately via time.Now, but does not enforce a window.
  • Not a FlatBuffers codec — report decoding lives on the TypeScript side, so this module stays small on purpose.

Cross-references