Files
neural-hunt/TESTING.md
jbergner bcfbef390f
Some checks failed
release-tag / release-image (push) Failing after 2m44s
RC-7
2026-08-11 16:58:07 +02:00

26 KiB
Raw 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

  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