gitprovandClaude Opus 4.8 a154849b3f Inventory polish: smaller phone/notebook, phone higher, auto-open, exit to board
The phone and notebook render a bit smaller; the phone sits higher on screen;
in-game the handset opens automatically (session mode); and the tool view
gains an EXIT ✕ that closes the inventory straight back to the board.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-22 20:17:08 +02:00
2026-08-22 19:49:44 +02:00
2026-08-14 12:43:11 +02:00
2026-08-14 12:43:11 +02:00
2026-08-14 12:43:11 +02:00

GUPI OSINT Board

A standalone, server-backed proof of concept for the Glitch University investigation desktop. Levels are authored data: the repository contains the engine and schema, not a compiled-in case.

The accepted normalized domain model and terminology are specified in docs/exhibit-data-model.md. PostgreSQL stores exhibits; frontend widgets visualize exhibit types.

Planned schema and exhibit work is tracked in docs/TODO.md. The intentionally narrow current demo is defined in docs/demo-scope.md: it opens directly on the board and focuses on investigation, flag-gated evidence reveals, and pasted screenshots.

Run locally

Requires Node 20 or 22 (Node 22 is used by the production container).

The self-contained development stack starts PostgreSQL, runs migrations, and serves the production build:

docker compose -f docker-compose.dev.yml up --build

Open http://localhost:8787. PostgreSQL is also exposed on localhost:5433 for inspection.

For Vite hot reload, start only the database, then run the server and web app natively:

docker compose -f docker-compose.dev.yml up -d db
cp .env.example .env
npm install
npm run migrate:up
npm run dev

Open http://localhost:5173; Vite proxies /api to port 8787.

Test

Run the fast domain tests without external services:

npm test

With the development PostgreSQL container running, exercise the migration ledger and persistence API against uniquely named disposable databases:

npm run test:integration

The integration suite drops only the temporary databases it creates. Override TEST_DATABASE_URL when PostgreSQL is not available at the development default on port 5433.

Run the isolated Chromium smoke test after installing its browser runtime once with npx playwright install chromium:

npm run test:e2e

The smoke harness builds the application, creates a disposable PostgreSQL database and level, exercises move, hand-pan, desktop wheel zoom, mobile touch pinch, folder expansion, file movement, and reload persistence, then removes the test database.

The browser suite also imports and solves the bundled Glass Harbor mystery against that disposable database. Mystery manifests are database-authoring content rather than compiled frontend cases; see mysteries/README.md.

Data and API

The server stores its tables and migration ledger in the dedicated osint schema of PostgreSQL. DATABASE_URL is required.

The accepted model stores each playable or editable level as an isolated mutable board of normalized exhibits. Immutable template-version boards are cloned to create levels, and mutable levels can be cloned back into new template versions. Assets remain immutable and reusable across those copies.

The running POC uses the canonical exhibit schema directly. The earlier JSON case store, widgets, and playthrough_* tables were removed in migration 006.

The API surface is:

  • GET /api/levels
  • POST /api/levels (editor only)
  • GET /api/templates
  • POST /api/templates/:slug/levels (instantiate a version; editor only)
  • GET /api/levels/:id
  • PUT /api/levels/:id
  • POST /api/levels/:id/reset
  • POST /api/levels/:id/templates (save a new immutable version; editor only)
  • POST /api/levels/:id/documents (player-uploaded evidence)
  • POST /api/levels/:id/reveals/seen
  • GET /api/levels/:id/flags (admin only)
  • PUT /api/levels/:id/flags/:key (admin only)
  • DELETE /api/levels/:id/flags/:key (admin only)
  • GET /api/levels/:id/evidence-match-rules (admin only)
  • POST /api/levels/:id/evidence-match-rules (editor only)
  • PUT /api/levels/:id/evidence-match-rules/:ruleId (editor only)
  • DELETE /api/levels/:id/evidence-match-rules/:ruleId (editor only)
  • GET /api/assets/:id
  • GET /api/session (verified session and admin capability summary)
  • GET /api/health

The browser also keeps a local emergency copy so a network interruption does not lose an in-progress board.

Level editor

Set LEVEL_EDITING_ENABLED=true and open /?edit=1 while signed in with a JWT carrying role: "admin". The shared auth_token cookie is verified with JWT_SECRET; the legacy isAdmin: true claim remains compatible. If the database is empty, this surface creates the first blank level. Editing and playing are the same operation against a mutable level. Authoring actions live under the Admin menu. Saving a template creates a new immutable version; instantiating gives every exhibit a fresh ID while sharing immutable binary assets. Reset restores the exact template version from which a level was created.

For the standalone development Compose stack, visit /api/dev/admin-session?returnTo=/?edit=1 once to receive a local signed admin cookie. This helper does not exist in production.

Files can be dragged from the desktop onto the board or selected with Add Document. Pasting a clipboard image creates a persisted image Document, which supports ordinary macOS and Windows screenshot workflows. Images, PDFs, and text files render inside document windows; unknown formats remain downloadable source files. Extracted evidence becomes an editable folder widget. Its editor controls the title, annotation, contained documents, and each source document's publication time. The default upload limit is 25 MB and can be changed with MAX_DOCUMENT_BYTES.

Image uploads are OCRed by the Tesseract executable bundled into the application image; text-file uploads use their text directly. Extracted text is stored against the immutable asset, copied into the Document's searchable body, and evaluated against level-authored fuzzy passage rules. A successful rule awards its configured flag and immediately participates in ordinary document reveals. Rules, anchors, per-anchor scores, and evaluation provenance are normalized PostgreSQL data—no case text is compiled into the engine. OCR is time-limited and failure-tolerant: the source remains on the board even when text extraction fails. OCR_LANGUAGES, OCR_TIMEOUT_MS, MAX_OCR_BYTES, and MAX_EXTRACTED_TEXT_CHARACTERS tune the worker; OCR_ENABLED=false disables image OCR without disabling uploads. Authors configure passages under Admin → Evidence Matching while editing a level.

In author mode, a Document may be assigned comma-separated reveal flags in its metadata editor. Play-mode level responses omit gated Documents until all requirements are earned. Admins can exercise the demo through Admin → Level Flags; newly delivered evidence receives a one-time arrival animation.

Production defaults editing to disabled. Set LEVEL_EDITING_ENABLED=true in /opt/gu_common/.env.prod only when the authoring surface should be available. JWT_SECRET is inherited from that shared environment, and authoring endpoints additionally require a verified admin claim.

POC exhibit and widget contract

The current POC defines four exhibit families and corresponding frontend widgets:

  • Folder — a titled, annotated collection. Opening it expands its contained source files to their saved relation-bound board positions; closing it retracts and hides them.
  • Source file — immutable binary evidence plus user-editable title, publication time, file type, and arbitrary key/value metadata. Image is the first fully visual file renderer. PDF, web capture, email, article, filing, price list, text, and generic file are registered types with a generic fallback until their renderers are built.
  • Note — investigator-authored interpretation, visually distinct from source evidence.
  • Event — an investigator-authored “this happened” statement with narrative text, an occurrence time, and normalized links to supporting exhibits. The editor manages citations, dashed support lines keep them visually distinct from red investigative thread, and chronologically ordered events form the reconstructed-story strip.

The Party family has distinct Person and Organization subtypes (businesses are organizations). Names begin as concepts in the level brief. The investigator classifies each concept, which creates the appropriate dossier exhibit and records the resolution. Expected classifications remain author-only. Dossiers store aliases and normalized evidence associations, but parties remain identity objects rather than special folders.

Red thread is a normalized connection between any two exhibits, including expanded source documents. Each connection may carry an investigator-authored relation tag, a persisted tightness percentage, and a presentation choice. LUGGAGE reuses the investigator note's expressive hanging tag, rotating to read before opening the editor; COMPACT keeps the original knot-and-label treatment and opens the editor directly. Both remain available while the POC establishes which works best. When a connected source document is retracted into its folder, the visible endpoint follows it to the folder until it is expanded again.

Folder ownership is stored as a normalized membership. The contained document exhibit owns its expanded xpos and ypos. For deterministic collapse behavior, one exhibit has at most one owning folder; two exhibits may reuse the same immutable asset when the same source file must appear in multiple folders.

An open folder draws a pale red containment band to each expanded file. Each dated file independently projects a grey line to the temporal index. This allows the player to arrange files until those grey lines are vertical, close the folder, and later reopen the same arrangement.

The timeline uses an optional board-level start and end date. Click Timeline in the menu or the displayed range in the footer to adjust it; Use Automatic Range returns to evidence-derived bounds. Configured bounds are normalized board data and follow template cloning and reset. Evidence outside the visible interval remains available and is pinned to the nearest timeline edge.

The typed frontend widget registry maps each exhibit type, and each document type, to its React visualization. Adding a domain type now produces a compile-time requirement to register its renderer. Widgets do not own investigation-domain data.

On desktop, an ordinary mouse wheel or two-finger trackpad scroll zooms the board. Desktop pinch gestures are consumed so they cannot zoom both the browser and board. Touch devices use a two-finger pinch on the board; toolbar controls remain available on every device.

Deploy at osint.glitch.university

The production service depends on gu_common: it joins the external gnommo Docker network, uses gnommo-db, shares its JWT secret and cross-subdomain authentication cookie, and is routed by the shared nginx container. Deploy gu_common after its nginx configuration changes, then deploy this repository:

./deploy.sh

The deploy script builds and syncs the application, reads production database credentials from /opt/gu_common/.env.prod, executes pending migrations in the osint schema, starts gnommo-osint-board, and waits for its health endpoint.

Deliberate POC boundaries

Authentication is supplied by the shared Glitch University account system. There is no OSINT-specific account model, real-world web browsing, or collaboration yet. OCR deliberately recognizes only evidence the player brings onto the board; it does not fetch or search the web.

S
Description
Glitch University Principal Investigator
Readme
22 MiB
Languages
TypeScript 99.3%
Shell 0.5%