[Foundry] Test (D337): a minimal Quest Board module pairs and round-trips in The Amberwake Accord #4

Closed
opened 2026-06-23 00:42:30 +00:00 by rbrooks · 4 comments
Owner

Rewritten 2026-11-06 for D337. This replaces "install and validate foundryvtt-rest-api": D333 chose our own module instead (#143). It runs before anything else in v5.0.

Where: the test Foundry instance from #143. Core 14.369.0, pf2e 8.7.0, world The Amberwake Accord (the-amberwake-accord). It's LAN only, at https://foundry-dev.lan.ryanbrooks.dev on docker-test (foundry-dev, data in /opt/foundry-dev/data). Users: Ryan (GM), Test-GM (GM), Player2 (player).

What gets built (throwaway; not product code)

  1. A minimal module quest-board-test: module.json, one ES module, and a settings field for the pairing code.
  2. A minimal endpoint that stands in for Quest Board:
    • it hands out a pairing code and swaps it for a token;
    • it accepts wss:// with that token;
    • it checks Origin;
    • it sends one command and logs what comes back.

Checks, each recorded here with evidence

  1. Outbound from a launched world: the module opens wss:// from inside the world (not just from the setup page). There's no CSP header on 14.369 (checked in #143); this confirms it in practice.
  2. Pairing: a code typed in by a GM gives a token that's scoped to the campaign. A wrong or expired code is refused.
  3. Command in: the server sends "create actor" (a pf2e NPC with a name and level), and the actor appears in the world. A repeated command id doesn't create a second actor.
  4. Event out: activating a scene in Foundry reaches the server with the scene's id and name.
  5. One holder: with Ryan and Test-GM both in the world, only one holds the link. When that one leaves, the other takes over within a few seconds.
  6. No player link: Player2 never connects, and can't pair.
  7. Reconnect: restart the endpoint; the module reconnects by itself and nothing runs twice.
  8. Record: the core and pf2e versions, the browser used, and anything surprising.

Where the endpoint runs: to be decided with the owner. It needs a wss:// address with a certificate the browser trusts, because the Foundry page is https://. Dev Quest Board runs published images only and must not be built from source.

Install, for the test: copy the module to /opt/foundry-dev/data/Data/modules/quest-board-test/, chown -R 1226:1226, and reload the world.

**Rewritten 2026-11-06 for D337.** This replaces "install and validate foundryvtt-rest-api": D333 chose our own module instead (#143). It runs before anything else in v5.0. **Where:** the test Foundry instance from #143. Core **14.369.0**, pf2e 8.7.0, world **The Amberwake Accord** (`the-amberwake-accord`). It's LAN only, at https://foundry-dev.lan.ryanbrooks.dev on docker-test (`foundry-dev`, data in `/opt/foundry-dev/data`). Users: `Ryan` (GM), `Test-GM` (GM), `Player2` (player). **What gets built (throwaway; not product code)** 1. A minimal module `quest-board-test`: `module.json`, one ES module, and a settings field for the pairing code. 2. A minimal endpoint that stands in for Quest Board: - it hands out a pairing code and swaps it for a token; - it accepts `wss://` with that token; - it checks `Origin`; - it sends one command and logs what comes back. **Checks, each recorded here with evidence** 1. **Outbound from a launched world:** the module opens `wss://` from inside the world (not just from the setup page). There's no CSP header on 14.369 (checked in #143); this confirms it in practice. 2. **Pairing:** a code typed in by a GM gives a token that's scoped to the campaign. A wrong or expired code is refused. 3. **Command in:** the server sends "create actor" (a pf2e NPC with a name and level), and the actor appears in the world. A repeated command id doesn't create a second actor. 4. **Event out:** activating a scene in Foundry reaches the server with the scene's id and name. 5. **One holder:** with `Ryan` and `Test-GM` both in the world, only one holds the link. When that one leaves, the other takes over within a few seconds. 6. **No player link:** `Player2` never connects, and can't pair. 7. **Reconnect:** restart the endpoint; the module reconnects by itself and nothing runs twice. 8. **Record:** the core and pf2e versions, the browser used, and anything surprising. **Where the endpoint runs:** to be decided with the owner. It needs a `wss://` address with a certificate the browser trusts, because the Foundry page is `https://`. Dev Quest Board runs published images only and must not be built from source. **Install, for the test:** copy the module to `/opt/foundry-dev/data/Data/modules/quest-board-test/`, `chown -R 1226:1226`, and reload the world.
Collaborator

Approach change: dev Foundry instance first (2026-07-27)

Decision from today's session: the Foundry pillar will be built against a dedicated dev Foundry instance, which does not exist yet. Standing one up is now the real first step of v4.0.0 — ahead of everything in this issue.

That partially supersedes this issue's "validate against the real instance, not a throwaway" instruction, so worth being precise about what still holds and what changes:

Still holds — and is now the thing to get right. The module is version-sensitive; that was the whole reason for insisting on the real instance. A dev instance is a perfectly good validation target provided it matches production's Foundry core version and module version. So the deliverable sharpens rather than weakens: record the tested (Foundry core version, foundryvtt-rest-api version) pair, and treat a drift between dev and prod on either as the compatibility risk to watch. A dev instance on a different core version would give false confidence — exactly what the original wording was guarding against.

Changes. Installing the module on production is no longer step one. Prod becomes a later verification step, once the adapter work is proven against dev.

Why a dev instance is worth the setup cost: this pillar writes into Foundry — actors, tokens, journal entries, live scenes (v4.2.0). Developing that against a live game world risks corrupting real campaign data, and there is no undo for a bad actor push. The same gap exists on the Discord side, incidentally: the dev bot has no DISCORD_BOT_TOKEN, so bot behaviour is currently only exercisable in production.

Suggested revised order for v4.0.0:

  1. Stand up a dev Foundry instance, matched to production's core version.
  2. Install and configure foundryvtt-rest-api on it; confirm reachability from the backend. (this issue)
  3. Document the tested core + module version pair in docs/. (this issue)
  4. Settle the connection topology and SSRF policy exemption — #143, which needs deciding before any connection code.
  5. Then the adapter work: #5, #6, #7, #8.
  6. Install and validate on production before the pillar ships.

No code has been written for this pillar yet, so nothing is invalidated by the change.

🤖 Generated with Claude Code

### Approach change: dev Foundry instance first (2026-07-27) Decision from today's session: **the Foundry pillar will be built against a dedicated dev Foundry instance**, which does not exist yet. Standing one up is now the real first step of v4.0.0 — ahead of everything in this issue. That partially supersedes this issue's "validate against the real instance, not a throwaway" instruction, so worth being precise about what still holds and what changes: **Still holds — and is now the thing to get right.** The module is version-sensitive; that was the whole reason for insisting on the real instance. A dev instance is a perfectly good validation target *provided it matches production's Foundry core version and module version*. So the deliverable sharpens rather than weakens: record the tested **(Foundry core version, `foundryvtt-rest-api` version)** pair, and treat a drift between dev and prod on either as the compatibility risk to watch. A dev instance on a different core version would give false confidence — exactly what the original wording was guarding against. **Changes.** Installing the module on production is no longer step one. Prod becomes a later verification step, once the adapter work is proven against dev. **Why a dev instance is worth the setup cost:** this pillar writes into Foundry — actors, tokens, journal entries, live scenes (v4.2.0). Developing that against a live game world risks corrupting real campaign data, and there is no undo for a bad actor push. The same gap exists on the Discord side, incidentally: the dev bot has no `DISCORD_BOT_TOKEN`, so bot behaviour is currently only exercisable in production. **Suggested revised order for v4.0.0:** 1. Stand up a dev Foundry instance, matched to production's core version. 2. Install and configure `foundryvtt-rest-api` on it; confirm reachability from the backend. *(this issue)* 3. Document the tested core + module version pair in `docs/`. *(this issue)* 4. Settle the connection topology and SSRF policy exemption — [#143](https://git.rhoving.com/rbrooks/Quest-Board/issues/143), which needs deciding before any connection code. 5. Then the adapter work: #5, #6, #7, #8. 6. Install and validate on production before the pillar ships. No code has been written for this pillar yet, so nothing is invalidated by the change. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Collaborator

Note, 27 Oct 2026: whether this is the step at all depends on #143. The owner wants every option weighed there, including a Quest Board Foundry module that connects back to the service. Don't start this before #143 is decided.

Note, 27 Oct 2026: whether this is the step at all depends on #143. The owner wants every option weighed there, including a Quest Board Foundry module that connects back to the service. Don't start this before #143 is decided.
claude-bot changed title from [Foundry P1] Install & validate foundryvtt-rest-api on the production Foundry instance to [Foundry] Test (D337): a minimal Quest Board module pairs and round-trips in The Amberwake Accord 2026-11-06 03:20:02 +00:00
Collaborator

Decided (owner, 2026-11-06, Q238; to be recorded as D338): the test's stand-in endpoint runs as a small throwaway container on docker-test, behind int-proxy at a LAN name (qb-foundry-test.lan.ryanbrooks.dev), set up in iac-repo the same way as foundry-dev, and removed after the test. Quest Board itself isn't touched, and dev keeps running published images only.

Plan

  1. Test code on a Quest-Board branch (spike/foundry-module), never merged:
    • tools/foundry-test/module/: the quest-board-test Foundry module;
    • tools/foundry-test/server/server.py: the stand-in, using websockets, with in-memory pairing codes and tokens, an Origin check, a command queue with ids, and a JSON-lines log.
  2. iac-repo PR, for the owner to approve:
    • the container on docker-test at host port 18131;
    • the int-proxy vhost qb-foundry-test.lan.ryanbrooks.dev, LAN only;
    • no public DNS, no backup, and a removal step.
  3. Run the eight checks above in The Amberwake Accord and record the results here.
**Decided (owner, 2026-11-06, Q238; to be recorded as D338):** the test's stand-in endpoint runs as a small throwaway container on docker-test, behind int-proxy at a LAN name (`qb-foundry-test.lan.ryanbrooks.dev`), set up in iac-repo the same way as foundry-dev, and **removed after the test**. Quest Board itself isn't touched, and dev keeps running published images only. **Plan** 1. **Test code** on a Quest-Board branch (`spike/foundry-module`), never merged: - `tools/foundry-test/module/`: the `quest-board-test` Foundry module; - `tools/foundry-test/server/server.py`: the stand-in, using `websockets`, with in-memory pairing codes and tokens, an `Origin` check, a command queue with ids, and a JSON-lines log. 2. **iac-repo PR**, for the owner to approve: - the container on docker-test at host port 18131; - the int-proxy vhost `qb-foundry-test.lan.ryanbrooks.dev`, LAN only; - no public DNS, no backup, and a removal step. 3. **Run the eight checks** above in The Amberwake Accord and record the results here.
Collaborator

Test results (2026-11-07, with the owner at the browser): all checks pass

Setup: Foundry 14.369 with pf2e 8.7.0, The Amberwake Accord, module quest-board-test 0.1.0, and the stand-in endpoint at wss://qb-foundry-test.lan.ryanbrooks.dev/ws (D338). Browser: the owner's. The endpoint's log is the evidence.

# Check Result
1 Outbound wss:// from a launched world ✅ connected from origin https://foundry-dev.lan.ryanbrooks.dev (00:29:46). No CSP got in the way.
2 Pairing ✅ Ryan (role 4) paired with a code, and the token is stored per user. Wrong, used and expired codes are refused (scripted tests).
3 Command in, applied once ✅ create_actor made "Quest Board Test Courier", a level 3 pf2e NPC (v1MyT9c5zO8g0Qgk), and the owner saw it. Re-sending the same id gave duplicate: true with the same actor, and it still did after a restart.
4 Event out ✅ activating "Test Scene" arrived as scene_activated {id: l7dMzZbEBIGr2kpV, name: "Test Scene"} from the holder.
5 One holder, handoff ✅ Test-GM paired and went to standby behind Ryan. Closing Ryan's window handed the link to Test-GM in the same second (00:34:27). A second tab of the same GM also waits on standby.
6 No player link ✅ Player2 never reached the endpoint: no connection and no pairing attempt, even after saving a valid code. The endpoint's own refusal of role 1 is covered by the scripted tests.
7 Reconnect, nothing twice ✅ After docker restart, Test-GM reconnected about 1 s after the endpoint was up, with no input. Both pairings and the acknowledged commands survived (state on disk).

Findings for the real module (v5.0, #5, #6, #7):

  1. restricted: true doesn't hide a client-scope setting from players. Player2 saw the "Pairing code" field. Register the pairing UI only for full GMs, as a settings menu (registerMenu with restricted: true) or with a check in ready, and say something to a non-GM rather than doing nothing silently.
  2. Status needs a lasting place. Foundry's notices fade, and the owner missed the reconnect notice while switching windows. The real module wants a small connection indicator, plus "Connected / Waiting (held by …) / Not connected" in its settings.
  3. The token per user works: each GM pairs once, and nothing a player can read holds it (world settings are readable by every client).
  4. Reconnect delay was the endpoint's own start-up (about 16 s, mostly pip installing at start). The module's backoff began at 1 s.

D333–D337 hold up as designed: our own module, connecting out, per-campaign tokens, one holder with handoff, and queued, idempotent commands. Closing. The real build starts with #5, #6 and #7. The stand-in endpoint can be removed whenever the owner likes (#877, set qb_foundry_test_enabled: false and apply).

## Test results (2026-11-07, with the owner at the browser): all checks pass **Setup:** Foundry **14.369** with pf2e **8.7.0**, The Amberwake Accord, module `quest-board-test` 0.1.0, and the stand-in endpoint at `wss://qb-foundry-test.lan.ryanbrooks.dev/ws` (D338). Browser: the owner's. The endpoint's log is the evidence. | # | Check | Result | |---|---|---| | 1 | Outbound `wss://` from a launched world | ✅ connected from origin `https://foundry-dev.lan.ryanbrooks.dev` (00:29:46). No CSP got in the way. | | 2 | Pairing | ✅ Ryan (role 4) paired with a code, and the token is stored per user. Wrong, used and expired codes are refused (scripted tests). | | 3 | Command in, applied once | ✅ `create_actor` made "Quest Board Test Courier", a level 3 pf2e NPC (`v1MyT9c5zO8g0Qgk`), and the owner saw it. Re-sending the same id gave `duplicate: true` with the same actor, and it still did after a restart. | | 4 | Event out | ✅ activating "Test Scene" arrived as `scene_activated {id: l7dMzZbEBIGr2kpV, name: "Test Scene"}` from the holder. | | 5 | One holder, handoff | ✅ Test-GM paired and went to **standby** behind Ryan. Closing Ryan's window handed the link to Test-GM **in the same second** (00:34:27). A second tab of the same GM also waits on standby. | | 6 | No player link | ✅ Player2 never reached the endpoint: no connection and no pairing attempt, even after saving a valid code. The endpoint's own refusal of role 1 is covered by the scripted tests. | | 7 | Reconnect, nothing twice | ✅ After `docker restart`, Test-GM reconnected about 1 s after the endpoint was up, with no input. Both pairings and the acknowledged commands survived (state on disk). | **Findings for the real module (v5.0, #5, #6, #7):** 1. **`restricted: true` doesn't hide a client-scope setting from players.** Player2 saw the "Pairing code" field. Register the pairing UI only for full GMs, as a settings menu (`registerMenu` with `restricted: true`) or with a check in `ready`, and say something to a non-GM rather than doing nothing silently. 2. **Status needs a lasting place.** Foundry's notices fade, and the owner missed the reconnect notice while switching windows. The real module wants a small connection indicator, plus "Connected / Waiting (held by …) / Not connected" in its settings. 3. **The token per user works:** each GM pairs once, and nothing a player can read holds it (world settings are readable by every client). 4. **Reconnect delay** was the endpoint's own start-up (about 16 s, mostly pip installing at start). The module's backoff began at 1 s. D333–D337 hold up as designed: our own module, connecting out, per-campaign tokens, one holder with handoff, and queued, idempotent commands. Closing. The real build starts with #5, #6 and #7. The stand-in endpoint can be removed whenever the owner likes (#877, set `qb_foundry_test_enabled: false` and apply).
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#4
No description provided.