Files
neural-hunt/TESTING.md
jbergner 1a56dec079
All checks were successful
release-tag / release-image (push) Successful in 3m57s
RC-16
2026-08-14 14:19:16 +02:00

36 KiB
Raw Permalink Blame History

Validation — V2.5.1 Reconnect / Presence / Heartbeat

Regression: regelmäßiger HTTP 409 nach ~90 Sekunden

  1. Browser-Client mit einem aktiven Task verbinden und mindestens 3 Minuten ohne F5 laufen lassen.
  2. Prüfen, dass Auto-Guesses über die frühere 90-Sekunden-Grenze hinweg weiterlaufen.
  3. DevTools → Network → WS beobachten: der Server sendet echte WebSocket-Ping-Control-Frames; Browser-Pongs verlängern die Read-Deadline.
  4. Netzwerk für einige Sekunden trennen und wieder aktivieren. Der Browser muss automatisch reconnecten und ohne F5 weitertippen.
  5. Während eines Task-Wechsels bzw. schnellen Reconnects darf ein verspätetes ReleasePresence der alten Verbindung die neue Presence nicht entfernen. Das wird zusätzlich durch TestPresenceReconnectOldReleaseCannotDeleteNewLease geprüft.
  6. Shell-Client ebenfalls mindestens 3 Minuten laufen lassen, Verbindung kurz unterbrechen und prüfen, dass [ws] wieder verbunden erscheint und Guessing fortgesetzt wird.

Maschinenlesbare 409-Codes:

presence_required
sequence_mismatch
task_config_changed
selection_conflict
task_inactive

V2.5 — Task-Auswahl, Vererbung und Shell-Client

Browser-Landing-Page

  1. Server mit mindestens zwei aktiven Tasks starten (ACTIVE_TASK_COUNT=2).
  2. / in einem neuen Browserprofil öffnen.
  3. Prüfen, dass vor der 3D-Ansicht die Task-Landing-Page erscheint.
  4. Einen Task wählen und prüfen, dass WebSocket/Auto-Guess starten.
  5. TASKS wählen, einen anderen Task anklicken und prüfen, dass keine identity already connected-Meldung entsteht.
  6. Browser neu laden: die serverseitig zuletzt gewählte Task-Karte muss als ausgewählt markiert sein.

Task-spezifisches NFT-Prompt + Admin-Draft

  1. /adminTASK → Task auswählen.
  2. Anzeigename, Beschreibung, NFT-Prompt-Anweisungen und Negative Prompt eintippen, noch nicht speichern.
  3. Mindestens einen Auto-Refresh-Zyklus (>3 s) abwarten und danach F5 drücken.
  4. Prüfen, dass die noch nicht gespeicherten Eingaben weiter im Formular stehen.
  5. TASK-KONFIG SPEICHERN verwenden und anschließend neu laden; nun müssen die Werte vom Server kommen.
  6. Task gewinnen oder über Admin close beenden.
  7. Folge-Task öffnen und prüfen, dass range_bits, task-spezifische Intervalle, Anzeigename, Beschreibung und beide Prompt-Felder identisch geerbt wurden.

Shell-Client interaktiv

go run ./cmd/client -url http://127.0.0.1:8080

Prüfen:

tasks
status
map 25
leaderboard 20
nfts 10
use 2
status

Der Shell-Client muss weiter automatisch Tipps senden. Nach use darf der Server die neue WebSocket-Verbindung nicht wegen der alten Presence ablehnen.

Shell-Client unbeaufsichtigt

go run ./cmd/client \
  -url http://127.0.0.1:8080 \
  -non-interactive \
  -task 1 \
  -max-nodes 250

Mindestens zwei Tippintervalle laufen lassen und im Admin prüfen, dass Guess-/Score-Aktivität vorhanden ist. Dann Ctrl+C; die Presence muss freigegeben werden.

Browser-/Shell-Identity-Portabilität

Im Browser eine Identity exportieren. Danach Browser-Tab schließen, damit die Single-Connection-Lease frei ist:

export NEURALHUNT_IDENTITY_PASSPHRASE='test-passphrase'
go run ./cmd/client \
  -import ./neuralhunt-identity-....json \
  -identity ./tmp-shell-identity.json

Die Client-ID muss mit der Browser-ID übereinstimmen.

Umgekehrt:

export NEURALHUNT_IDENTITY_PASSPHRASE='test-passphrase'
go run ./cmd/client \
  -identity ./tmp-shell-identity.json \
  -export ./tmp-browser-import.json

Diese Datei anschließend über Import in einem Browserprofil laden; die Client-ID muss identisch bleiben.

Automatische Tests

Zusätzlich zu den bisherigen Tests:

go test ./cmd/client

enthält PBKDF2-Testvektor sowie Raw-/verschlüsselten Identity-Roundtrip. internal/data/successor_test.go prüft mit einem echten SQLite-Treiber die Task-Vererbung, Idempotenz des Nachfolgers und das atomare Migrieren der Client-Auswahl.

In dieser Erstellungsumgebung erfolgreich ausgeführt:

node --check internal/webui/dist/app.js

gofmt -w cmd/client/*.go internal/data/store.go internal/server/server.go internal/artifact/worker.go

# kompletter Source-Compile mit lokalen API-Stubs für die drei nicht ladbaren Module
go test -run '^$' ./...

# echte Shell-Client-Krypto-/Identity-Tests; nur der WebSocket-Import ist dabei gestubbt
go test ./cmd/client

Zusätzlich wurde eine V2.4-artige SQLite-Datenbank mit Python/SQLite angelegt und die Upgrade-Reihenfolge aus OpenSQLite() reproduziert. Dabei wurden die fünf neuen Task-Spalten, client_task_selection und der erst nach parent_task_id angelegte tasks_parent_unique_idx erfolgreich erzeugt. Das ist relevant, weil der Index bei Bestandsdatenbanken nicht vor dem ALTER TABLE angelegt werden darf.

Ergebnis dieser ausführbaren Prüfungen: erfolgreich.

Statisch geprüft / implementiert

  • UI ist weiterhin vollständig unter internal/webui/dist/ eingebettet; kein npm/Vite-Build nötig.
  • /leaderboard ist eine SPA-Route und besitzt einen öffentlichen Live-WebSocket.
  • Mobile Mode ist für Client, Leaderboard und Admin implementiert; Admin bietet mobile MAP/TASKS/CONTROL-Navigation.
  • Das Leaderboard lädt Gewinner-Artefakte ausschließlich über /api/public/artifacts/{task}/preview.
  • Die öffentlichen Leaderboard-Daten enthalten NFT-Anzahl und letzte Wasserzeichen-Preview, aber keine Original-Artifact-URI.
  • Der frühere öffentliche /artifacts/*-File-Server ist entfernt; Original und Manifest benötigen ein Admin-JWT.
  • Raster-Previews werden serverseitig mit einem eingebrannten Bitmap-Wasserzeichen neu als PNG codiert; SVG-Previews erhalten eine wiederholte Wasserzeichenebene.
  • TARGET FIELD bildet Score monoton und direkt auf den Orbitalradius ab; Kameratiefe kann die wahrgenommene Nähe nicht mehr umkehren.
  • Score-Ringe: 0 / 25 / 50 / 75 / 90 / 95 / 99+; Vorder- und Rückhälfte werden zur Tiefenwahrnehmung unterschiedlich gerendert.
  • Pseudozufällige Client-zu-Client-Synapsen sind vollständig entfernt.
  • SIGNALWEGE verbinden ausschließlich Top-Kandidaten/eigenen Client mit dem Task-Core; Score-Verbesserungen erzeugen nur entlang dieses realen Pfads Signalimpulse.
  • Client lädt bei task_changed Range Bits, Seed, Revision, Intervalle und Punkte neu.
  • Client-Autosubmit verwendet keinen statischen setInterval mehr, sondern die jeweils aktuelle Task-Konfiguration.
  • Task-Aktionen werden in task_actions persistiert, jede Sekunde geprüft und mit Status/Audit gespeichert.
  • Alte SQLite-Datenbanken erhalten die neuen tasks-Spalten per ALTER TABLE-Migration.
  • set_range_bits unterstützt preserve und reroll.
  • Task-spezifische Tippintervalle überschreiben globale Runtime-Defaults.
  • OpenAI-, ComfyUI- und A1111-Provider speichern nur lokal erzeugte Ergebnisbytes + Manifest; kein S3 notwendig.
  • OpenAI-Provider validiert für gpt-image-2 die dokumentierten Dimensionsgrenzen, bevor ein Request gesendet wird.
  • Provider-Secrets/URLs werden nicht über die Runtime-Settings-API exponiert.
  • ComfyUI unterstützt Workflow-Platzhalter für Prompt/Negative Prompt/Seed/Größe/Steps/Modell.
  • A1111 unterstützt optional Basic Auth und Checkpoint-Override pro Request.

In dieser Umgebung nicht vollständig ausführbar

go test ./... und go mod tidy können hier nicht abgeschlossen werden, weil ausgehender DNS/Netzwerkzugriff auf proxy.golang.org gesperrt ist. Der Versuch scheitert beim Download dieser bereits im go.mod referenzierten Module:

modernc.org/sqlite
github.com/go-chi/chi/v5
github.com/gorilla/websocket

Dadurch konnten internal/data, internal/server, internal/ws und das komplette Binary hier nicht gegen frisch heruntergeladene Dependencies gebaut werden. Die direkt von ihnen unabhängigen geänderten Go-Packages wurden kompiliert/getestet.

Docker ist in der Erstellungsumgebung ebenfalls nicht verfügbar.

Empfohlene Tests auf deinem Rechner

go mod tidy
go test ./...
go run ./cmd/server

Dann:

  1. / öffnen und prüfen, dass TARGET FIELD standardmäßig aktiv ist. Einen Score-90-, Score-95- und Score-99-Punkt vergleichen: 99 muss unabhängig von Orbit/Neigung sichtbar auf dem kleinsten Ring liegen.
  2. ORBIT laufen lassen und die Ansicht ziehen: Die Reihenfolge der Ringradien darf sich durch Kamerabewegung nicht umkehren.
  3. SIGNALWEGE einschalten: Es dürfen keine Client→Client-Kanten erscheinen; nur ausgewählte Client→Task-Pfade sind erlaubt.
  4. Zwei unabhängige Browserprofile auf 127.0.0.1 verbinden.
  5. /leaderboard in einem dritten Tab öffnen; Punktänderungen sollen ohne manuelles Reload erscheinen.
  6. Im Admin-Bereich einen aktiven Task auswählen und pause sofort ausführen. Clients müssen PAUSE anzeigen und keine Tipps senden.
  7. resume ausführen; Autosubmit muss mit dem aktuellen Client-Intervall weiterlaufen.
  8. set_range_bits im Modus preserve erhöhen; Client muss neue Bitzahl ohne Reload verwenden.
  9. set_range_bits im Modus reroll ausführen; Punkte/Scores müssen auf 0 zurückfallen und neuer Seed/Revision aktiv werden.
  10. Eine Range-Bit-Änderung fünf Minuten in die Zukunft planen und anschließend abbrechen; Status cancelled prüfen.
  11. Zwei geplante Aktionen hintereinander setzen, z. B. 24 Bit und später 32 Bit, und Ausführung/Audit prüfen.
  12. Einen kleinen Zahlenraum verwenden, einen Task lösen und lokales SVG + Manifest prüfen.
  13. /leaderboard öffnen: das Gewinner-Artefakt muss in Galerie und Ranking nur als Wasserzeichen-Vorschau erscheinen.
  14. Direkter Aufruf der früheren /artifacts/...-URI muss 404 liefern; Original/Manifest aus dem Admin-Dashboard müssen weiterhin nach Authentifizierung öffnen.
  15. Browser auf <850 px setzen oder MOBILE aktivieren: Client-Details müssen einklappbar sein; im Admin müssen MAP/TASKS/CONTROL einzeln erreichbar sein.

OpenAI Provider

ARTIFACT_PROVIDER=openai
ARTIFACT_MODEL=gpt-image-2
OPENAI_API_KEY=...
ARTIFACT_WIDTH=1024
ARTIFACT_HEIGHT=1024
ARTIFACT_QUALITY=medium

Task lösen bzw. bei einem abgeschlossenen Task NFT-Bild neu erzeugen ausführen. Erwartet:

/data/artifacts/artifact_<id>/image.png
/data/artifacts/artifact_<id>/manifest.json

ComfyUI Provider

ComfyUI starten, API-Workflow exportieren und im Workflow mindestens {{PROMPT}} sowie optional die anderen Platzhalter verwenden. Danach:

ARTIFACT_PROVIDER=comfyui
COMFYUI_URL=http://127.0.0.1:8188
COMFYUI_WORKFLOW_PATH=/path/workflow-api.json
ARTIFACT_MODEL=checkpoint.safetensors

AUTOMATIC1111 Provider

A1111 mit --api starten:

ARTIFACT_PROVIDER=a1111
A1111_URL=http://127.0.0.1:7860
ARTIFACT_MODEL=checkpoint.safetensors

Bei --api-auth user:pass zusätzlich A1111_USER und A1111_PASSWORD setzen.

Regressionen aus früheren Fixes

  • Auth challenge/login toleriert optionale JWK-Metadaten (alg, use, kid etc.).
  • Mehrere Challenges derselben Identität überschreiben einander nicht.
  • Eine Browser-ID darf weiterhin nur eine aktive Client-WebSocket-Verbindung besitzen.
  • Leere Listen werden als [], nicht als null, an die UI geliefert.
  • Client-Punkt wird direkt beim WebSocket-Connect erzeugt.

Additional v2 checks

  • SQLite schema parsed successfully with Python sqlite3, including task_actions and the live task override columns.
  • The legacy migrations/001_init.sql mirror was synchronized with internal/data/schema.sql so manual schema inspection does not show an obsolete layout.
  • Responsive CSS keeps the Admin Task Actions / Artifact control plane visible on common laptop widths (8511280px).

V2.1 package-integrity regression

The V2 archive was missing internal/data/, which caused an old store.go to survive when users extracted over a previous checkout. That produced compile errors such as missing Task.GuessMinIntervalSec, Task.Paused, LiveLeaderboard, and TaskAction. V2.1 explicitly includes:

  • internal/data/store.go
  • internal/data/schema.sql
  • the matching V2 internal/server/server.go

Always test from a clean extraction directory.

V2.2 zusätzliche Validierung

  • node --check internal/webui/dist/app.js erfolgreich.
  • go test ./... wurde zusätzlich gegen lokale Compile-Stubs für chi, gorilla/websocket und modernc.org/sqlite ausgeführt, um alle Neural-Hunt-Packages inklusive internal/server/watermark_test.go ohne Netzwerkzugriff zu kompilieren.
  • watermark_test.go prüft Traversal-Abwehr, SVG-Wasserzeichen und Raster-Wasserzeichen.
  • Die erweiterten Leaderboard-/Artifact-SQL-Abfragen wurden gegen das reale schema.sql mit Python sqlite3 ausgeführt.

V2.4 Scale smoke test

node --check internal/webui/dist/app.js
go test ./internal/runtime ./internal/ws ./internal/server ./cmd/loadtest

# Server in Terminal 1
go run ./cmd/server

# Start small, then increase.
go run ./cmd/loadtest -url http://127.0.0.1:8080 -clients 250 -ramp 10s -duration 1m -max-nodes 100
go run ./cmd/loadtest -url http://127.0.0.1:8080 -clients 1000 -ramp 30s -duration 2m -max-nodes 100

Im Admin unter /admin sollten dabei Guess/s, Improve/s, SQLite W/s, WS Frames/s, WS MB/s, Drops/s, Goroutines und Heap MB live aktualisiert werden. Bei einem stabilen Test sollten Drops/s nahe 0 bleiben. Ein niedriger SQLite W/s relativ zu Guess/s bestätigt, dass verlierende Tipps den DB-Hotpath nicht mehr belasten.

V2.5.2 Admin form stability regression

The periodic 3-second admin telemetry refresh must never rebuild #settingfields. Only explicit task/tab changes or completed admin actions may refresh the control plane.

Manual regression:

  1. Open /admin and select ARTIFACT.
  2. Open the Provider or Quality native select and leave it open for more than 6 seconds.
  3. The dropdown must remain open while Overview/Performance counters continue updating.
  4. Type into Prompt-Zusatz for more than 6 seconds; caret position and textarea scroll position must not jump.
  5. Switch tabs and back; the draft value must still be restored.

Static/syntax checks used for this release:

node --check internal/webui/dist/app.js
go test ./internal/webui ./internal/core ./internal/auth ./internal/artifact ./internal/settings

RIFT Medium + OpenAI usage/cost telemetry

  • Start with ARTIFACT_PRESET=raccoon_full_art_v1 and no explicit ARTIFACT_QUALITY: the effective quality must be medium.
  • Generate one RIFT winner artifact and verify that the OpenAI request sends quality=medium.
  • Verify that artifact_api_usage receives one artifact row per successful winner-image API call and a character_anchor row when the canonical anchor is created. A successful call is logged even if the provider omits its optional usage block; in that case token counts remain zero and estimated_cost_usd is NULL.
  • When OpenAI returns usage data, verify that input_tokens, text/image input split, output_tokens, total_tokens, estimated_cost_usd, and pricing_basis are persisted and mirrored in the artifact manifest/provider metadata.
  • In Admin → Artifact, verify KOSTEN HEUTE, Ø KOSTEN PRO KARTE, KOSTEN PRO 1.000 KARTEN, and the recent-call table. Anchor cost belongs to today's total but must not affect the per-card average.
  • Verify GET /api/admin/artifact/usage?day_start_ms=<local-midnight-ms> returns the same aggregates.

RIFT V2.8 — per-task style references + manual anchor

  • In Admin → ARTIFACT, with OPENAI_API_KEY configured and no existing _collection/character_anchor.png, click RIFT-ANCHOR JETZT ERZEUGEN. Verify exactly one OpenAI image-generation request is made, the file is stored at data/artifacts/_collection/character_anchor.png, the usage row has kind=character_anchor, and the UI changes to LOCKED without an overwrite button.
  • Verify a second direct POST /api/admin/artifact/character-anchor returns HTTP 409 while the anchor exists.
  • In Admin → TASK ACTIONS upload a JPEG/PNG style reference. Verify it is stored content-addressed under data/artifacts/_styles/, tasks.nft_style_reference receives only the basename, and the admin preview shows the uploaded image.
  • Verify the public Task landing card displays /api/public/tasks/<id>/style-reference and marks a custom task as TASK STYLE; tasks without an upload display the bundled default reference.
  • Complete a task and verify its successor inherits nft_style_reference.
  • Generate a RIFT winner card and inspect the multipart OpenAI edit request: image[] must contain two files in this order: character_anchor.png, then the task style reference. Provider metadata should contain reference_mode=character-plus-task-style, the character-anchor hash and the style-reference hash.
  • Use AUF DEFAULT ZURÜCK and verify the task DB reference is empty and generation falls back to internal/artifact/assets/style_reference.jpg without deleting shared content-addressed style files.

V3.1 — Admin cleanup for stale non-winner profiles

  • In Admin → RUNTIME set e.g. 30 Tage and click PRÜFEN. Verify the preview reports only clients whose clients.last_seen is older than the cutoff, that are not currently connected, and that have never appeared as tasks.winner_client_id.
  • Keep an old client connected via WebSocket: it must be reported as aktuell verbunden geschützt and never be deleted.
  • Create an old winner identity: it must be reported as Gewinner geschützt and never be deleted, regardless of age.
  • Confirm deletion and verify the client row is removed together with cascading task_points, client_unlocks, and client_task_selection rows.
  • Verify a recent non-winner remains untouched.
  • Verify WebSocket connect and disconnect update clients.last_seen, so a long-running session starts its inactivity window at disconnect rather than at its original login.
  • The API rejects cleanup windows shorter than one hour.

Relevant automated tests: internal/data/profile_cleanup_test.go and internal/server/profile_cleanup_test.go.

V3.5 Random Guess Lottery

  1. Admin → RUNTIME: Lotterie-Zeitfenster (s)=20, Max. gezogene Tipps je Task/Fenster=2 setzen und speichern.
  2. Mindestens drei Clients mit demselben Task verbinden und innerhalb desselben Fensters je einen Tipp absenden lassen.
  3. Bis zum Fensterende müssen die Requests auf die Losziehung warten. Danach dürfen höchstens zwei Clients Tipp gezogen & geprüft sehen; übrige Clients sehen Tipp diesmal nicht gezogen.
  4. Admin-Telemetrie: Reject/s steigt für nicht gezogene Tipps; Guess/s zählt nur tatsächlich gezogene/ausgewertete Tipps.
  5. Mit Max. gezogene Tipps je Task/Fenster=0 speichern: Tipps müssen wieder ohne Lotterie-Verzögerung normal verarbeitet werden.
  6. Bei mehreren aktiven Tasks prüfen, dass jeder Task sein eigenes Kontingent erhält.

V3.8 security/hardening checks

The production server exposes the public app on HTTP_ADDR (default :8080) and the private control plane on ADMIN_HTTP_ADDR (default :8081). Verify that the public listener returns 404 for /admin and /api/admin/settings, while the private listener serves /admin and requires the HttpOnly admin session cookie.

For large synthetic load tests, temporarily set sybil_pow_bits=0 and sybil_warmup_sec=0 from the private Admin Runtime page (or use the matching DEFAULT_... values on a fresh database). Otherwise the load generator intentionally pays the same new-identity proof-of-work as a real Sybil client.

Useful browser checks after HTTPS proxying:

/public: Content-Security-Policy and X-Frame-Options: DENY are present
/admin:  document.cookie does not expose neuralhunt_admin_session (HttpOnly)
WS:      normal browser websocket connects; a foreign browser Origin is rejected

The OpenAI circuit breaker uses rolling windows and successful usage rows. Set very small limits in Admin Runtime to verify that a winning artifact stays pending with an OpenAI cost circuit breaker active diagnostic instead of making another provider call. Restore the desired limits afterward.

V3.9 portable identity + owned artifact recovery

  1. Start the CLI with a persistent identity path and note the printed Client-ID and file path:
NEURALHUNT_IDENTITY=/tmp/nh-client/identity.json go run ./cmd/client -url http://127.0.0.1:8080
  1. Run identity and verify the same Client-ID/path is shown. Restart with the same file and verify the Client-ID is unchanged.
  2. Set a passphrase of at least 12 characters and create an encrypted browser-compatible backup:
export NEURALHUNT_IDENTITY_PASSPHRASE='correct horse battery staple'
go run ./cmd/client -identity /tmp/nh-client/identity.json -export /tmp/neuralhunt-browser-import.json
  1. In the browser choose IDENTITÄT → IMPORT, select that JSON, enter the same passphrase and confirm the switch. The browser Client-ID must exactly equal the CLI Client-ID. The import validates that public/private P-256 keys can sign/verify and that the optional export Client-ID matches.
  2. After a win is fully rendered, MEINE NFTS must list the artifact and ORIGINAL must download the unwatermarked artifact. A different identity requesting /api/me/artifacts/<task-id>/download must receive 404.
  3. In the CLI, my-nfts lists only the authenticated identity's ready winner artifacts and nft original <task-id> <datei> downloads the original. nfts / nft get remain public watermarked previews.
  4. Verify legacy encrypted exports without format/clientId/iterations metadata still import using 250,000 PBKDF2 iterations.

Focused identity tests can run without the optional network/database dependencies:

go test ./cmd/client/identity.go ./cmd/client/identity_test.go
node --check internal/webui/dist/app.js

V4.0 Beacon Hunt + Hosted Service smoke test

Beacon Hunt

  1. Start the game and enable Guess Lottery Max Accepted > 0 plus Beacon Hunt in Admin Runtime.
  2. Open two browser/CLI identities and choose different paths (PULSE, FLUX, ORBIT).
  3. Confirm requests wait until the window closes and that /api/public/beacon/<task-id>/latest returns the recorded drand round, signature, derived randomness and boosted path.
  4. Disable Beacon Hunt again and confirm the legacy crypto/rand lottery still works.

Hosted reward pairing

For a direct local go run test (not Compose), set CS_GAME_PUBLIC_URL=http://127.0.0.1:8080 and CS_GAME_ADMIN_URL=http://127.0.0.1:8081. On Customer Service startup, verify the log says Hosted-Code/Reward-Control-Plane bereit. A warning about DNS/connection, HTTP 404, or CUSTOMER_SERVICE_SHARED_SECRET must be fixed before generating a one-shot code.

  1. Build with make images-compose, then start with docker compose --profile hosted up -d and route 8090 publicly over HTTPS; keep 8081/8091/8092 private.
  2. Create/log in to a Customer Service account.
  3. Log into the normal Neural Hunt browser with the desired reward identity and click HOSTED CODE (or run hosted-code in the CLI).
  4. Paste the one-shot code into the Customer Service portal. Confirm the portal shows the proven Client-ID. Reusing the same code must fail.

Protected manual credits

  1. Set CS_ALLOW_MANUAL_CREDITS=1 only in a test/private environment.
  2. Log into the Customer Service admin on private port 8091 and grant a small amount of credits.
  3. Verify the customer ledger contains a positive manual_test_grant entry.
  4. Set CS_ALLOW_MANUAL_CREDITS=0 again and verify grants are rejected.

Managed workers / prepaid stop

  1. Create a worker and assign an active task and Beacon path.
  2. Start it. The worker should create/register its own P-256 identity and the game should install worker -> reward owner delegation.
  3. Let the worker win a test task (use a deliberately tiny range only in a private test environment). Confirm winner_client_id is the main reward owner while winner_worker_client_id is the worker identity.
  4. Reduce the prepaid balance to less than one billable minute and verify Customer Service stops the worker before another paid minute is allowed.
  5. Stop/start the worker and confirm its Docker identity volume preserves the same Client-ID.
  6. Download the worker identity, replace it with another valid raw identity, start again and confirm a new worker Client-ID registers while reward ownership remains delegated to the same main identity.

PayPal Sandbox

  1. Keep PAYPAL_ENVIRONMENT=sandbox and configure sandbox client ID/secret/webhook ID.
  2. Buy the smallest test package from the Customer Service portal.
  3. Confirm credits are added only after a server-confirmed COMPLETED capture whose amount and currency match the stored package.
  4. Repeat the return/capture callback or webhook and verify the paypal:<order-id> ledger reference prevents duplicate credits.
  5. Leave live mode disabled until the provider/legal review for the actual product is complete.

V4.1 split Docker images

  1. Build the three roles locally:
docker buildx bake --load
  1. Verify the image contents/entrypoints:
docker image inspect neuralhunt-server:local --format '{{json .Config.Entrypoint}}'
docker image inspect neuralhunt-customer-service:local --format '{{json .Config.Entrypoint}}'
docker image inspect neuralhunt-worker:local --format '{{json .Config.Entrypoint}}'

Expected entrypoints are /app/neuralhunt, /app/neuralhunt-customer-service and /app/neuralhunt-client respectively. The server image should not contain the client or Customer Service binaries.

  1. For local Hosted Service testing, build/tag the worker image before starting the hosted profile:
make images-compose
docker compose --profile hosted up -d
  1. Set CS_WORKER_IMAGE to a deliberately missing public image with CS_WORKER_AUTO_PULL=true; starting a worker should cause Docker Engine to pull the configured image. Repeat with CS_WORKER_AUTO_PULL=false; the request must fail before prepaid credits are charged.

  2. For a private registry, configure a read-only token with CS_WORKER_REGISTRY_SERVER/USERNAME/PASSWORD, remove the local worker image and confirm an on-demand pull succeeds. Verify the credentials do not appear in the managed worker container environment (docker inspect).

Focused stdlib-only tests for the Docker image orchestration can be run even without the application's external Go dependencies:

go test ./internal/customer/docker.go ./internal/customer/docker_test.go

V4.2.5 worker identity-volume migration check

After publishing the new worker image, start a hosted worker that already owns an identity volume from an older release. It must start without permission denied, keep the same client identity, and remain writable at /identity/identity.json. Do not delete the named volume for this test.

V4.2.6 Customer engagement/admin smoke test

  1. Start Game + Customer Service with the same CUSTOMER_SERVICE_SHARED_SECRET. In Docker ensure the Game container has:
CUSTOMER_SERVICE_INTERNAL_URL=http://customer-service:8092
  1. In private Customer Admin set a small Startguthaben, e.g. 3, register a fresh customer and confirm its first balance is exactly 3 Credits and the compact ledger contains Startguthaben.
  2. Set a small Reward pro positivem Worker-Tipp, link a main reward identity, start a Hosted Worker, and let it produce evaluated guesses. A guess earns a reward only when it raises that Worker's personal best. Lottery-rejected/non-evaluated tickets and non-improving guesses must not add credits.
  3. Stop Customer Service temporarily while a Hosted Worker gets a personal-best improvement, then restart it. Confirm the Game DB's pending outbox is delivered later and the Customer ledger receives the reward exactly once.
  4. Set PAYPAL_ENABLED=false; after reload the public portal must not show Credits kaufen and must not request package data as part of normal portal loading.
  5. Generate enough minute/reward entries to verify the Customer credit history groups repeated rows by UTC day and reason. Confirm the raw credit_ledger rows still exist separately in SQLite.
  6. In private Customer Admin block a test user. Existing Customer sessions must become unauthorized, all its Worker leases must be revoked/stopped, and login must return 403 until the user is unblocked.
  7. Disable Benutzer-Anmeldung erlauben. New login attempts must receive 503; an already-authenticated, non-blocked session intentionally remains valid. Re-enable afterwards.
  8. Disable Neue Registrierungen erlauben and confirm both challenge/registration are rejected. Re-enable and set PoW to a small test value (e.g. 8) to verify the browser solves the challenge automatically.
  9. Enable Registrierung nur mit einmaligem Invite-Code, create an invite in Customer Admin, register one account with it, then verify the same invite cannot create a second account.

For production/open registration, do not rely on PoW as a one-human/one-account guarantee. Keep Traefik/Caddy rate/concurrency limits on registration/login routes and consider invite-only registration when free signup credits have meaningful value.

V4.2.8 Admin control / ownership smoke tests

  1. Customer Admin: enable LIMIT AUFHEBEN for one account, create more workers than CS_MAX_WORKERS_PER_CUSTOMER, and verify only global inventory/running caps still stop creation/start.
  2. Disable the bypass and verify the normal per-customer limits apply again.
  3. Server Admin: set an active task to AUSLAUFEND, complete/close it, and verify there is no child task with parent_task_id=<retired-id>; if the active pool needs replacement, a new root task may appear.
  4. Transfer a ready collectible to another client identity. Verify the new identity sees it under MEINE NFTS, the old identity no longer does, while the task's historical winner_client_id is unchanged.
  5. Server Admin → Artifact: queue an ADMIN NFT DROP for a client. Verify the new drop_* task is completed/pending, artifact generation proceeds through the normal worker/circuit breaker, and the resulting collectible appears for the target identity without incrementing historical wins.

V4.2.9 Worker recovery / image rollout smoke tests

  1. Einen Hosted Worker starten und dessen Client-ID notieren.
  2. Die Verbindung zwischen Worker und Game Server länger als 90 Sekunden unterbrechen. Nach Wiederherstellung muss derselbe Container/WebSocket ohne neue Identity wieder aktiv werden; bei einem abgelaufenen JWT erfolgt automatisch ein kryptografisches Re-Login.
  3. Einen Task abschließen lassen und den Game Server kurz während der Successor-Erstellung/Abfrage stören. Der Worker muss den Taskwechsel weiter versuchen und darf nicht nach einem einzelnen Fehler ohne WebSocket stehen bleiben.
  4. Einen Worker auf einer alten, inzwischen abgeschlossenen Task-ID neu starten. Für Hosted Worker muss der CLI auf die serverseitig ausgewählte aktive Task zurückfallen und dieselbe Identity weiterverwenden.
  5. Im Customer Admin bei einem Worker IMAGE UPDATE ausführen. Vorher laufend -> danach wieder laufend; vorher gestoppt -> danach weiterhin gestoppt. /identity/identity.json und Worker-Client-ID müssen erhalten bleiben.
  6. Bei einem mutable Tag (z. B. worker_latest) ein neues Registry-Image publizieren und IMAGE LADEN · ALLE WORKER AKTUALISIEREN verwenden. Docker muss den Tag erneut pullen, obwohl er lokal bereits existiert.
  7. Einen Worker bewusst über Portal/Admin stoppen. Die Docker-Restart-Policy unless-stopped darf ihn nicht wieder starten. Bei gesperrtem Benutzer oder widerrufenem Lease muss /internal/workers/register mit 409 ablehnen.

V4.3 Service Controller smoke test

  1. Start Customer Service with CS_WORKER_ORCHESTRATION_MODE=controller, a 32+ character SERVICE_CONTROLLER_SHARED_SECRET, and no mounted docker.sock.
  2. Start one Service Controller on a worker host with matching secret, stable SC_ID, SC_MASTER_URL pointing to Customer Service :8092, and SC_ADVERTISE_URL pointing to the controller's private :8102 address.
  3. In Customer Admin, verify the controller becomes ONLINE and its heartbeat/capacity appears.
  4. Create/start a customer worker. Verify its controller_id is the registered host and the actual container exists only on that host.
  5. Download/upload the worker identity from the normal Customer Portal; both operations must be proxied through the assigned controller.
  6. Use per-worker IMAGE UPDATE; verify the image is pulled/recreated on the remote host while the same named identity volume remains.
  7. Put the controller into DRAIN; existing workers remain controllable, but a new worker must be placed on another online controller (or fail cleanly if none exists).
  8. Stop the controller process and wait longer than CS_CONTROLLER_OFFLINE_AFTER; Master admin must show OFFLINE and must not assign new workers there.
  9. Use the controller emergency UI on :8101 over VPN to stop/restart a local managed worker and to execute ALLE STOPPEN.
  10. Verify direct mode still works unchanged with CS_WORKER_ORCHESTRATION_MODE=direct and local docker.sock.

V4.4 Neural Place

Zusätzlich zu den bestehenden Tests sollte für Neural Place mindestens Folgendes geprüft werden:

  1. Eine echte Best-Score-Steigerung erzeugt gemäß place_points_per_score Place-Punkte; ein schlechterer Tipp erzeugt keine Punkte.
  2. Eine Score-Steigerung eines identity_delegations-Workers erhöht ausschließlich das Wallet des verknüpften Owners; place_progress_events.source_client_id bleibt die Worker-ID.
  3. Zwei Placements ziehen zweimal place_pixel_cost ab, erhöhen die globale Revision monoton und schreiben zwei place_pixel_events auch wenn derselbe Pixel übermalt wird.
  4. Ein Placement ohne ausreichendes Guthaben liefert insufficient_place_points und verändert weder Wallet noch Canvas-Revision.
  5. /api/public/place liefert nach Neustart denselben Canvas-Zustand; /api/public/place/changes?after=<rev> holt Live-Lücken nach.
  6. /place synchronisiert Placements über /api/place/ws und fällt bei Verbindungsunterbrechungen auf den HTTP-Delta-Sync zurück.
  7. Ein alter Place-Teilnehmer mit verdientem Guthaben oder Placements wird von der Admin-Profilbereinigung nicht gelöscht.

Der Data-Layer enthält dafür TestPlaceWorkerProgressCreditsOwnerAndFundsPlacements; der Server-Cleanup enthält TestProfileCleanupProtectsPlaceParticipant.

V4.5 Place Reward Economy

Zusätzlich zu den V4.4-Place-Tests:

  1. Ein tatsächlich gezogener Lotterie-Tipp erhält exakt place_draw_points; derselbe Task/Revision/Client/Seq-Reward ist idempotent.
  2. Im Beacon-Modus erhält ein gezogener Tipp auf dem Boost-Pfad bei aktiviertem place_draw_beacon_multiplier exakt place_draw_points × effektives Beacon-Gewicht.
  3. Ein gezogener Tipp ohne Score-Verbesserung behält seinen Draw-Bonus; eine zusätzliche Score-Verbesserung erzeugt separat den normalen Progress-Reward.
  4. Ein verknüpfter Hosted Worker schreibt Draw- und Zeit-Rewards in das Owner-Wallet, bleibt aber als source_client_id im Audit sichtbar.
  5. Der erste gültige signierte Request startet nur die Aktivzeitmessung. Nach mindestens place_time_interval_sec gezählter aktiver Zeit werden place_time_points pro vollem Intervall gutgeschrieben.
  6. Eine Lücke größer als place_time_max_gap_sec wird nicht nachträglich als aktive Zeit vergütet.
  7. Doppelte/alte Sequenznummern dürfen die Aktivzeit nicht vorwärts bewegen. Nach einem Task-Reroll wird aufgrund der neuen Task-Revision eine neue Zeit-State-Reihe verwendet.
  8. POST /api/admin/place/points akzeptiert nur vorhandene Identitäten und positive Gutschriften; Worker-Ziele werden auf den Owner aufgelöst und der Grund wird in place_bonus_events protokolliert.
  9. /api/place/me führt Progress-, Draw-, Zeit- und Admin-Earnings gemeinsam chronologisch auf.
  10. Werte 0 für place_draw_points bzw. place_time_points deaktivieren nur die jeweilige Reward-Quelle, ohne die übrige Place-Ökonomie zu beeinflussen.

Der Data-Layer enthält dafür TestPlaceDrawTimeAndAdminRewardsCreditDelegatedOwner. Das Long-Idle-Verhalten der prozesslokalen Aktivzeitmessung wird zusätzlich im Server-Smoke-Test über eine Pause oberhalb von place_time_max_gap_sec geprüft.