533 Commits

Author SHA1 Message Date
9e58fd0c6d Merge pull request 'ci: clean up after image builds and demo wipes' (#237) from chore/ci-self-cleanup into main
Some checks failed
sync-project-tree / sync (push) Successful in 22s
Build container images / build (push) Successful in 2m49s
Build container images / deploy (push) Successful in 51s
SonarQube / analysis (push) Successful in 10m16s
Demo wipe / wipe (push) Failing after 11m56s
Reviewed-on: #237
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-11-04 12:16:02 +00:00
ebb8f870bb ci: clean up after image builds and demo wipes
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 53s
PR Checks / client-build (pull_request) Successful in 2m19s
PR Checks / server-tests (pull_request) Successful in 6m19s
The build job runs docker on the runner host's daemon, so every run left
four tagged images behind for good. Remove this run's tags once they are
pushed and cap BuildKit's cache at 5 GB, always, never failing the job.

The demo wipe pulls every two hours; drop the dangling image each pull
replaces. The uom-deploy-runner deploy job is deliberately untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-11-02 11:20:47 -06:00
27f7fe5d85 Merge pull request 'fix(events): core.lease keeps a module's retry: false on read/apply refusals' (#236) from fix/lease-retry-206 into main
Some checks failed
sync-project-tree / sync (push) Successful in 13s
Build container images / build (push) Successful in 1m47s
Build container images / deploy (push) Successful in 40s
SonarQube / analysis (push) Failing after 3m20s
Demo wipe / wipe (push) Successful in 7m23s
Reviewed-on: #236
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-11-02 11:21:52 +00:00
10efafbae5 Merge branch 'main' into fix/lease-retry-206
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 52s
PR Checks / client-build (pull_request) Successful in 1m10s
PR Checks / server-tests (pull_request) Successful in 6m14s
2026-11-02 11:15:17 +00:00
36ee31896b Merge pull request 'fix(safety): team notifications skip members who block the author' (#234) from fix/safety-team-notifications into main
Some checks failed
sync-project-tree / sync (push) Successful in 17s
Build container images / build (push) Failing after 3m45s
Build container images / deploy (push) Has been skipped
SonarQube / analysis (push) Has been cancelled
Reviewed-on: #234
2026-11-02 11:14:54 +00:00
28d0b76f13 fix(events): core.lease keeps a module's retry: false on read/apply refusals
All checks were successful
PR Checks / client-build (pull_request) Successful in 59s
PR Checks / bot-tests (pull_request) Successful in 1m16s
PR Checks / server-tests (pull_request) Successful in 6m3s
core.lease rebuilt both failure envelopes as { ok: false, error }, so a
module's retry: false was dropped and the dispatcher retried a permanent
refusal (e.g. module-rust's events-disabled) three times. Both returns now
pass retry: false through and leave retry undefined otherwise, so the
dispatcher's default still applies. Three tests in eventModuleContract cover
read, apply (reservation still released) and the default.

Closes #206

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-11-01 02:53:17 -06:00
673701c91b fix(safety): team notifications skip members who block the author
All checks were successful
PR Checks / client-build (pull_request) Successful in 1m14s
PR Checks / server-tests (pull_request) Successful in 6m4s
PR Checks / bot-tests (pull_request) Successful in 56s
#233 left this open: the forum hid a blocked author's posts from a
member, but the member was still pushed, put in the inbox and emailed
about them.

- teamNotify.forumPost excludes every member who blocks the post's
  author, or the member who started the thread (the forum 404s that
  thread for them). One recipient list, so push, the inbox and email
  skip the same people. userSafety.blockersOf answers it, and a staff
  author is blocked by nobody (S8). A failed lookup notifies without it.
- The daily digest applies each recipient's forumFilter at send time:
  blocked authors, blocked thread starters, hidden threads and posts.
  digestPostsSince now also returns the thread's created_by.
- A reply's notify payload carries threadAuthorUserId.

The Team activity feed needed nothing: forum posts are never written to
it (membership, renames and module game events only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-31 00:23:18 -05:00
f13b8fe1fe Merge pull request 'feat(safety): report, block and hide, MODULE_API 1.21.0' (#233) from feature/user-safety into main
All checks were successful
sync-project-tree / sync (push) Successful in 19s
Build container images / build (push) Successful in 1m34s
Build container images / deploy (push) Successful in 39s
SonarQube / analysis (push) Successful in 10m40s
Demo wipe / wipe (push) Successful in 7m47s
Reviewed-on: #233
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-30 12:24:36 +00:00
41a1e84b51 feat(safety): report, block and hide, MODULE_API 1.21.0
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 36s
PR Checks / server-tests (pull_request) Successful in 6m11s
PR Checks / client-build (pull_request) Successful in 49s
USER_SAFETY.md step 2 (core), with S11-S14 decided while building it.

- Reports: POST /player/reports takes any target type, core's forum
  types or a module's registered ones. One resolver
  (model/safety/targets.js) answers "what is this item, for this
  reader" and 404s an item the reader cannot see. Every report stores
  content_reports.snapshot; the queue shows a module item from it.
- Hide for everyone: POST /admin/moderation/reports/:id/hide hides the
  item through its own moderation (forum hide as staff, or the module's
  hide) and actions the report with actioned_with = 'hide'.
- Blocks (user_blocks) name an item, never a person, and land on the
  author's account first (S12); an unlinked game identity is blocked as
  itself and becomes an account block once linked (S14). Staff cannot
  be blocked; a block on someone later promoted stops applying (S8).
- Hide for me (user_hidden_items), /auth/me/blocks and /auth/me/hidden.
- Forum reads filter blocked authors and hidden items on the server and
  carry canBlock per thread and post.
- MODULE_API 1.21.0: api.registerReportTargets,
  api.registerGameIdentities, ctx.safety.forViewer and
  ctx.safety.staffAmong, so a module filters its own per-viewer feeds on
  the server (S11) and marks lines canBlock (S13).
- Web: Hide for me / Block author in the forum menus, a Blocked section
  on both account pages, Hide for everyone in the Reports queue.
- Tests, OpenAPI, route/guard/engagement manifests regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-30 05:41:00 -05:00
7bd93b8bb0 Merge pull request 'feat(account): self-service account deletion, MODULE_API 1.20.0' (#232) from feature/account-deletion into main
Some checks failed
sync-project-tree / sync (push) Successful in 26s
SonarQube / analysis (push) Successful in 11m44s
Build container images / build (push) Failing after -9s
Build container images / deploy (push) Has been skipped
Demo wipe / wipe (push) Successful in 7m8s
Reviewed-on: #232
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-30 01:34:52 +00:00
d1ae8f943f feat(account): self-service account deletion, MODULE_API 1.20.0
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 37s
PR Checks / client-build (pull_request) Successful in 49s
PR Checks / server-tests (pull_request) Successful in 6m14s
ACCOUNT_DELETION.md phase 2 (core): a member deletes their own account on
the website, and the admin route runs the same procedure.

- DELETE /api/v1/auth/me/account: typed username, password always (AD7),
  and a 2FA or recovery code when 2FA is on. A wrong password or code is
  400, not 401 (AD12), so the app's refresh-on-401 never replays it.
  409 password_required and last_admin; the demo's shared logins are
  locked.
- auth/accountDeletion.js: module hooks first, then one transaction that
  reserves a staff username (reserved_usernames, AD9) and deletes the row,
  then a Teams reconcile, the audit entry (no username) and a notice to
  the old address (new protected template auth.account-deleted).
- MODULE_API 1.20.0: api.onUserDeleting({ userId, deletedBy }), before the
  delete, 10 s limit, cannot veto (AD10, AD11).
- utils/displayAuthor.js: a deleted account's username snapshots read
  "[deleted player]" to members and the stored name, marked, to staff
  (AD5, AD6). Used by the forum, the Reports queue, the guest list and
  the Team digest. A deleted account drops off Forum guests (AD13).
- Reserved names refuse at every username write, answered as "taken".
- Web: Delete my account on the player and staff Account pages; staff
  views mark "(deleted account)".
- OpenAPI regenerated; route manifest and engagement manifest regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-29 18:32:19 -05:00
222bcb02fc Merge pull request 'fix(demo): switch on the actions the seeded events use (DM28)' (#231) from fix/demo-seed-enables-actions into main
Some checks failed
sync-project-tree / sync (push) Successful in -2m16s
Build container images / build (push) Successful in 1m39s
Build container images / deploy (push) Successful in 39s
SonarQube / analysis (push) Failing after 6m19s
Demo wipe / wipe (push) Failing after 12m53s
Reviewed-on: #231
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-28 09:55:24 +00:00
ebedee146f fix(demo): switch on the actions the seeded events use (DM28)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 54s
PR Checks / server-tests (pull_request) Successful in 6m6s
PR Checks / client-build (pull_request) Successful in 7m33s
The step-5 walk found the demo's three seeded events (Server greeting,
Treasure drop, Payday) showing as ready but failing when run: a fresh
database has no switchboard rows, so every change and irreversible action
starts disabled ("not enabled on this deployment").

seed:demo now switches on, with the events, every action their steps use,
uncapped, as an admin flipping the switch without filling the cap boxes
would. Nothing else is switched on. The deny list (DM11) still keeps a
visitor's own command step harmless, and the wipe drops the rows with the
rest of the database.

Checked on a scratch database with the v0.6.1 module: the seed leaves
rust.command.run, rust.dt.start and rust.coins.pay enabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-28 04:44:25 -05:00
47a79d841c Merge pull request 'feat(demo): seed the default set's events, NPCs and chat titles' (#230) from feature/demo-default-events into main
Some checks failed
sync-project-tree / sync (push) Successful in 14s
Build container images / build (push) Successful in 1m14s
Build container images / deploy (push) Successful in 59s
SonarQube / analysis (push) Failing after 6m13s
Demo wipe / wipe (push) Successful in 7m7s
Reviewed-on: #230
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-28 02:21:04 +00:00
62eaa7693d Merge branch 'main' into feature/demo-default-events
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 40s
PR Checks / client-build (pull_request) Successful in 1m8s
PR Checks / server-tests (pull_request) Successful in 12m26s
2026-10-28 02:05:42 +00:00
0f05098d8f Merge pull request 'feat(demo): refuse a data: URI in any write body (DM25)' (#229) from feature/demo-data-uri-lock into main
Some checks failed
sync-project-tree / sync (push) Successful in 15s
Build container images / build (push) Successful in 1m34s
Build container images / deploy (push) Successful in 41s
SonarQube / analysis (push) Failing after 6m18s
Demo wipe / wipe (push) Successful in 7m15s
Reviewed-on: #229
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-28 01:28:28 +00:00
8ec7f399a7 Merge branch 'main' into feature/demo-data-uri-lock
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 41s
PR Checks / client-build (pull_request) Successful in 55s
PR Checks / server-tests (pull_request) Successful in 6m25s
2026-10-28 01:21:50 +00:00
38aef5964e feat(demo): seed the default set's NPC profiles, factions and chat titles (DM26)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 47s
PR Checks / client-build (pull_request) Successful in 1m8s
PR Checks / server-tests (pull_request) Successful in 12m27s
The restored rig's RunicNPC says it is already managed, so a fresh site
adopts none of its profiles and its first push empties them; chat styles
are never adopted either. They come from the fixture now, through
Module-Rust's onDemoSeed (needs the release carrying DM26; an older module
ignores these keys):

- npcs.factions: bandits and guards, allied;
- npcs.profiles: bandit (roamer, rnpc_raider) and guard (sentry, heavy,
  rnpc_campguard, heals), both on the demo server. Only the fields that
  differ from RunicNPC's defaults are listed;
- chat: [VIP] in gold and [MOD] in green.

The baseline's placements (bandit-1, bandit-2, guard-1) spawn again once
the site pushes these.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-27 20:16:27 -05:00
9408fee3c1 feat(demo): seed two showcase events, Treasure drop and Payday
All checks were successful
PR Checks / client-build (pull_request) Successful in 1m35s
PR Checks / server-tests (pull_request) Successful in 6m41s
PR Checks / bot-tests (pull_request) Successful in 7m14s
The demo default set (org lead, 2026-10-27: curated showcase) puts its
permissions and NPCs on the rig, where the baseline restores them; events
live in the site's database, which every wipe drops, so they belong in the
fixture. Both are manual, published, on the demo server:

- Treasure drop: rust.dt.start, Easy.
- Payday: rust.coins.pay, 500 coins to every linked player, said in chat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-27 13:35:55 -05:00
c18250eb07 Merge pull request 'fix(demo): chown modules/ and logs/ from a root container in the wipe' (#228) from fix/demo-wipe-chown into main
Some checks failed
sync-project-tree / sync (push) Successful in 12s
Build container images / build (push) Successful in 2m22s
Build container images / deploy (push) Successful in 47s
SonarQube / analysis (push) Failing after 8m32s
Demo wipe / wipe (push) Successful in 7m22s
Reviewed-on: #228
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-27 18:15:24 +00:00
61feca50a8 feat(demo): refuse a data: URI in any write body (DM25)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 45s
PR Checks / client-build (pull_request) Successful in 1m2s
PR Checks / server-tests (pull_request) Successful in 6m4s
Uploads are locked hard on the demo, but an image pasted into rich text
does not use multipart: it is saved as a data: URI inside the JSON body.
refuseDataUri, mounted beside refuseMultipart, refuses any POST/PUT/PATCH/
DELETE whose body has a data: URI in any string, on any route, core's or
a module's, with the usual 403 DEMO_LOCKED.

Each string is first read the way a browser reads an attribute (HTML
character references decoded, tabs and newlines removed), so &#100;ata: and
a data: split by a newline are refused too. "metadata:" and prose such as
"Data: none, yet" pass. Reads are never checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-27 13:13:51 -05:00
7aafd0dd74 fix(demo): chown modules/ and logs/ from a root container in the wipe
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 40s
PR Checks / server-tests (pull_request) Successful in 6m1s
PR Checks / client-build (pull_request) Successful in 7m27s
The runner user is not root, so the host chown failed and `|| true` hid it.
The app (uid 1000) then could not unpack the declared module into ./modules,
came up healthy with no module, and seed:demo refused the fixture's
rust.command.run event (website run 358, the first scheduled wipe).

A throwaway container runs as root and the runner is in the docker group, so
the chown now happens there, and a failure stops the run at that step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-27 13:04:44 -05:00
3b9998539f Merge pull request 'fix(docker): move the app and bot images to Node 22.23.4 (Node2Shell)' (#226) from fix/node22-node2shell into main
Some checks failed
sync-project-tree / sync (push) Successful in 15s
Build container images / build (push) Successful in 4m56s
Build container images / deploy (push) Successful in 1m3s
SonarQube / analysis (push) Failing after 7m37s
Demo wipe / wipe (push) Failing after 1m52s
Reviewed-on: #226
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-27 17:40:01 +00:00
3c1c174337 Merge branch 'main' into fix/node22-node2shell
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 45s
PR Checks / client-build (pull_request) Successful in 1m5s
PR Checks / server-tests (pull_request) Successful in 6m35s
2026-10-27 17:33:14 +00:00
247323ff05 Merge pull request 'feat(demo): deploy/demo and the wipe + baseline workflows (step 5)' (#227) from feature/demo-deploy into main
Some checks failed
Demo wipe / wipe (push) Failing after 25s
sync-project-tree / sync (push) Successful in 33s
Build container images / build (push) Successful in 2m18s
Build container images / deploy (push) Successful in 51s
SonarQube / analysis (push) Has been cancelled
Reviewed-on: #227
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-27 17:31:45 +00:00
d48d8c473c feat(demo): deploy/demo and the wipe + baseline workflows (step 5)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 50s
PR Checks / client-build (pull_request) Successful in 57s
PR Checks / server-tests (pull_request) Successful in 12m25s
The public demo's deployment (docs/website/DEMO_MODE.md §7, §10 step 5):

- deploy/demo/docker-compose.yml: the app and its database only, DEMO_MODE
  on, a /status healthcheck, the fixture mounted for seed:demo, MODULES read
  from state/modules.env so the workflow never edits the .env with secrets.
- deploy/demo/.env.example: every variable the host needs, including
  BOT_INTERNAL_KEY (the app refuses to start in production without it) and
  MODULE_SOURCE_HOSTS (a fresh database's install allowlist).
- deploy/demo/seed.json: the four logins, settings, two posts, a wiki page,
  a published "Server greeting" event on a Rust console step, the rig's
  server link and demo-player's Steam link; secrets as "$env:NAME".
- deploy/demo/rig.mjs: plan (latest Rust bundle + newest Module-Rust on the
  same protocol, keeping the previous set when none matches), restore (kill,
  write a changed bundle, delete + unpack demo-baseline.tar.gz, start),
  baseline (power stop saves the world, DM22; compress, rename, start) and
  smoke (the site, then the rig reporting in), on the Pterodactyl client API.
- .gitea/workflows/demo-wipe.yml: every 2 h on the hour UTC, on a push to
  deploy/demo/, and by hand; rig first, then down -v / pull / up / seed.
- .gitea/workflows/demo-baseline.yml: by hand only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-26 12:23:52 -05:00
2a6e307f16 fix(docker): move the app and bot images to Node 22.23.4 (Node2Shell)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 1m1s
PR Checks / client-build (pull_request) Successful in 2m13s
PR Checks / server-tests (pull_request) Successful in 7m19s
CVE-2026-111497 ("Node2Shell") is an out-of-bounds write in the Brotli
decoder Node bundles; it was fixed on 2026-10-21 in 22.23.4, 24.22.1 and
26.12.1. Both images were on node:20-alpine, and Node 20 is past end of
life, so it gets no fix.

The site is not reachable through the main path (Express 4 / body-parser
1.20 refuse a `br` request body with 415, and nothing calls the zlib
Brotli API), but fetch() decodes Brotli responses and the runtime itself
was unpatched. Pin the exact patched version so a floating tag cannot
hold an old build, and run CI on the same version the image ships.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-26 10:00:02 -05:00
c7a24a186c Merge pull request 'fix(demo): refuse publishing the homepage hero (DM21)' (#225) from fix/demo-hero-publish-lock into main
Some checks failed
sync-project-tree / sync (push) Successful in 25s
Build container images / build (push) Successful in -13s
Build container images / deploy (push) Successful in 47s
SonarQube / analysis (push) Failing after 6m25s
Reviewed-on: #225
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-26 14:50:55 +00:00
c87563bc32 fix(demo): refuse publishing the homepage hero (DM21)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 47s
PR Checks / client-build (pull_request) Successful in 54s
PR Checks / server-tests (pull_request) Successful in 12m42s
Settings stay open on the demo (DM2), so the hero editor's Publish wrote
hero_layout, the hero every visitor sees. hero_layout joins the protected
settings keys: PUT /admin/settings naming it answers 403 DEMO_LOCKED with
keys: ['hero_layout']. The editor's draft autosave and Preview write only
hero_layout_draft and stay open; its uploads were already refused by the
multipart lock. Server lock only, as decided: the editor needs no change,
since its status line and the toast already show the demo sentence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-26 09:35:03 -05:00
69998ec548 Merge pull request 'feat(demo): refuse an event step at save with demoRefuses (MODULE_API 1.19.0)' (#224) from feature/demo-step-refusal into main
Some checks failed
sync-project-tree / sync (push) Successful in 25s
Build container images / build (push) Successful in 3m17s
Build container images / deploy (push) Successful in 45s
SonarQube / analysis (push) Failing after 8m36s
Reviewed-on: #224
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-26 10:52:08 +00:00
cee4ab8aea feat(demo): refuse an event step at save with demoRefuses (MODULE_API 1.19.0)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 46s
PR Checks / server-tests (pull_request) Successful in 7m0s
PR Checks / client-build (pull_request) Successful in 7m29s
DEMO_MODE.md §3.4 refuses a denied console command when the event is
saved and when it runs. Saving is core's route, and core never asked a
module about a step's params, so the save-time half had no seam (DM18).

An event action may now declare demoRefuses(params), returning a
sentence or null. With DEMO_MODE on, create, save and publish ask it
for every step; the first sentence refuses the write 403 DEMO_LOCKED
with that sentence as the message (and in errors). A hook that throws
refuses too. Off the demo it is validated and never called, and the
catalog strips it with the other callables.

The events controller passes code and message through on create,
update and publish, so the client's one DEMO_LOCKED toast fires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-26 03:39:42 -05:00
c6f53a1509 Merge pull request 'feat(demo): DEMO_MODE lock, seed:demo and the demo banner (MODULE_API 1.18.0)' (#223) from feature/demo-mode-core into main
Some checks failed
sync-project-tree / sync (push) Successful in 26s
Build container images / build (push) Successful in 2m46s
Build container images / deploy (push) Successful in 50s
SonarQube / analysis (push) Failing after 6m26s
Reviewed-on: #223
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-24 00:05:08 +00:00
4ad746c43e feat(demo): DEMO_MODE lock, seed:demo and the demo banner (MODULE_API 1.18.0)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 43s
PR Checks / client-build (pull_request) Successful in 1m3s
PR Checks / server-tests (pull_request) Successful in 8m37s
Core half of the public demo (docs/website/DEMO_MODE.md §10 step 1, website#222).
Nothing here runs unless DEMO_MODE=true.

- server/src/demo/: the lock at /api/v1, ahead of every router. It covers every
  Tier A row of §3.3, the shared-login conditions, POST /auth/register (DM16),
  DELETE /auth/me/sessions/:id on a demo account (DM17), and the PUT
  /admin/settings keys that reach a Tier A lock. Any multipart request is refused
  app-wide. noindex and robots.txt. A condition that cannot decide refuses.
- Loader: demoLocks in module.json (writes only, under the module's own mounts),
  ctx.demoMode, and api.onDemoSeed (DM15). MODULE_API 1.18.0 on both halves.
- GET /public/settings carries demo: { resetsAt, logins } on the demo only.
- npm run seed:demo -- <fixture>: upserts the four accounts, sets the site live
  with registration disabled, seeds content on the first run only, calls each
  module's onDemoSeed, and stamps demo_state. It resolves $env: strings and
  refuses to run off the demo.
- Client: the banner, one DEMO_LOCKED toast fed from the request primitive (so
  module screens get it too), and the published logins on both sign-in pages.
  Dashboard and register say the demo message instead of blaming a role.
- OpenAPI: DemoLocked schema, the demo block, and a note that any write may
  answer DEMO_LOCKED on the demo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-23 18:52:09 -05:00
b5e0d01a91 Merge pull request 'fix(teams): say "game server", not "shard", on the never-synced Teams panel' (#221) from fix/teams-panel-neutral-wording into main
All checks were successful
sync-project-tree / sync (push) Successful in 19s
Build container images / build (push) Successful in 1m55s
Build container images / deploy (push) Successful in 41s
SonarQube / analysis (push) Successful in 10m49s
Reviewed-on: #221
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-22 19:59:38 +00:00
8012a457fc fix(teams): say "game server", not "shard", on the never-synced Teams panel
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 39s
PR Checks / client-build (pull_request) Successful in 58s
PR Checks / server-tests (pull_request) Successful in 12m43s
Admin → Teams told an operator that an unsynced projection "is not a
confirmed empty shard". Teams is a core primitive and core's word for the
host is the game server; "shard" is Ultima Online's. The detail now reads
"What is shown below does not confirm that the game server has no Teams."

The matching test, and the four comments that said "shard" about the same
state, follow it. Found while making runicgateway.com's core docs
game-neutral (runicgateway.com#34), which had to paraphrase this line
rather than quote it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-22 14:43:37 -05:00
6890a74c7c Merge pull request 'chore(release): cut edge over to main — Steam sign-in (MODULE_API 1.17.0) and the last fixes' core half' (#220) from edge into main
All checks were successful
Build container images / build (push) Successful in -21s
sync-project-tree / sync (push) Successful in -2m6s
Build container images / deploy (push) Successful in 39s
SonarQube / analysis (push) Successful in 12m57s
Reviewed-on: #220
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-22 10:29:20 +00:00
56c6a279dc Merge pull request 'fix(core): the last fixes' core half (W1 W2 W5 W19)' (#219) from fix/last-fixes-core into edge
All checks were successful
PR Checks / client-build (pull_request) Successful in 1m10s
PR Checks / server-tests (pull_request) Successful in 6m15s
PR Checks / bot-tests (pull_request) Successful in 7m21s
Reviewed-on: #219
2026-10-22 06:18:20 +00:00
f40dd765c3 fix(core): the last fixes' core half (W1 W2 W5 W19)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 49s
PR Checks / client-build (pull_request) Successful in 1m4s
PR Checks / server-tests (pull_request) Successful in 6m6s
Stage 4 of docs/modules/rust/PLAN_LAST_FIXES.md, with the org lead's
2026-10-22 decisions.

W19: one module per site at boot, as at install. The first module
directory by name holds the site, whether or not it then loads; every
other is recorded startup_failed at the manifest stage, before a line of
its code is required, with a reason naming the module that holds the
site. Module-uo and Module-Rust side by side used to fail the second
with a slot collision. Loader, lifecycle, schema and event-contract
tests that loaded several fixture modules now load each case alone.

W1: a reconcile files a resource the game no longer has, and that is
past its `until`, as expired rather than orphaned, with the same log
line expired() writes. No MODULE_API bump: `until` and reconcile's
answer are unchanged.

W5: a dry run that passes on the draft is kept with the fingerprint of
the validated spec (three nullable columns on event_definitions).
Publish stamps the new version verified, with that dry run's reviewer
and time, when the spec it freezes has the same fingerprint, and writes
version.verified on the occurrences it re-pins. Verify answers
draftRecorded and publish answers verified; OpenAPI regenerated.

W2: the run console says "Phase p1: steps done, waiting to advance" and
"Why this phase has not advanced", not "has not started".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-22 00:38:39 -05:00
9a1654fc54 Merge pull request 'feat(auth): sign in with Steam (MODULE_API 1.17.0)' (#218) from feat/steam-signin into edge
Reviewed-on: #218
2026-10-20 15:25:59 +00:00
02897030e5 test(auth): a linked Steam account signs in through the mobile bridge
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 39s
PR Checks / client-build (pull_request) Successful in 54s
PR Checks / server-tests (pull_request) Successful in 12m40s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-19 20:33:11 -05:00
5e398d8acd feat(auth): sign in with Steam (MODULE_API 1.17.0)
Steam is a built-in provider beside Google and Discord (STEAM_SIGNIN.md,
D392-D396). OpenID 2.0: the assertion is verified server to server with
check_authentication, return_to must match byte for byte, and the claimed id
must be a 17-digit SteamID64. A Steam Web API key is required to enable it
(D394); persona name and avatar are stored on the identity and refreshed at
each sign-in, and a failed lookup never blocks one. Steam never signs anybody
up (D395), on the web or the mobile bridge. Several Steam accounts may be
linked; DELETE /auth/me/account/identities/:provider takes ?id= to unlink one.

MODULE_API 1.17.0: ctx.identities.list, api.onIdentityLinking (refuse-only,
fails closed, module text never shown) and api.onIdentityLinked. No unlink
hook (D393). Schema additive; OpenAPI, routes and engagement manifests
regenerated with no modules installed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-19 14:02:49 -05:00
6ba2ed90ed Merge pull request 'feat(events): a dry run carries a module's advice (MODULE_API 1.16.0)' (#217) from feat/dry-run-warnings into main
All checks were successful
sync-project-tree / sync (push) Successful in 13s
Build container images / build (push) Successful in -14s
Build container images / deploy (push) Successful in 56s
SonarQube / analysis (push) Successful in 10m52s
Reviewed-on: #217
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-17 06:32:10 +00:00
8c30cbbddd feat(events): a dry run carries a module's advice (MODULE_API 1.16.0)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 43s
PR Checks / server-tests (pull_request) Successful in 6m8s
PR Checks / client-build (pull_request) Successful in 7m58s
An action answering verify: true with { ok: true, detail: { warnings } } has
each line shown as a warning finding (code `advice`) that never fails the
dry run. Strings only, at most ten per step, each cut at 500 characters;
anything else under warnings is ignored, and a refused step carries none.

Rust PLAN_REDESIGNS §11.4, D391: a raid base's spot is checked against the
world as it stands now (a player on it, a rock beside it), which may change
before the run, so the editor shows it as advice. One optional reading of an
existing member, so minor; Module-uo's ^1.10.0 still resolves and says no
warnings. STEAM_SIGNIN.md's claim moves to 1.17.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-16 15:18:53 -05:00
0f29f0acb2 Merge pull request 'feat(events): a trigger can start a run (Rust §14.3 stage 9, D370)' (#216) from feat/event-trigger-starts into main
All checks were successful
sync-project-tree / sync (push) Successful in 21s
Build container images / build (push) Successful in 1m31s
SonarQube / analysis (push) Successful in 9m14s
Build container images / deploy (push) Successful in 56s
Reviewed-on: #216
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-16 07:07:59 +00:00
c9e992588b feat(events): a trigger can start a run
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 35s
PR Checks / client-build (pull_request) Successful in 44s
PR Checks / server-tests (pull_request) Successful in 12m45s
Rust PLAN_REDESIGNS §14.3.12 (D370), stage 9 of §14.3. A definition's
published version may carry `starts`: a trigger, a condition and a
cooldown. A matching firing creates a run of it, now.

- spec: `starts` validated by the phase gate's own function (same
  grammar, same dormant rule; a dormant start refuses publish), a
  cooldown from 0m to 7d (30m default), at most ten, event.* refused.
- events/starts.js: the emit path's third subscriber, beside dispatch
  and the gate observer. An unverified version starts nothing; one run
  at a time per definition and the cooldown are one conditional UPDATE
  on the new event_trigger_starts table; the occurrence key makes one
  firing reported twice one run. Ignored firings are counted by why.
- event_runs.origin: the firing (trigger, label, time, subject, matched
  condition, declared variables), also on the run.created audit line.
- Editor: a "Started by the game" panel using the gate's condition
  builder, with the ignored-firings line. Run console: "Started because
  ... fired".
- No module contract change and no MODULE_API bump.

Tests: eventStarts (server 21, client 8); eventRunnerSql +7 against a
real MariaDB (75/75); Module-uo proof on uo.champ.boss_up against the
dev database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-16 01:51:55 -05:00
eb773693d3 Merge pull request 'feat(events): a failed step may say when to retry (MODULE_API 1.15.0)' (#215) from feat/event-step-retry-after into main
Some checks failed
sync-project-tree / sync (push) Successful in 12s
Build container images / build (push) Successful in 1m38s
SonarQube / analysis (push) Successful in 9m29s
Build container images / deploy (push) Has been cancelled
Reviewed-on: #215
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-15 07:47:46 +00:00
769794d183 feat(events): a failed step may say when to retry (MODULE_API 1.15.0)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 48s
PR Checks / client-build (pull_request) Successful in 59s
PR Checks / server-tests (pull_request) Successful in 6m6s
A module's perform() may answer { ok: false, retry: true, retryAfterMs }
and the runner waits that long instead of the flat EVENT_STEP_RETRY_MS
(Rust PLAN_REDESIGNS §14.3.13, D371). Dangerous Treasures' warm-up is the
first user.

- dispatch.classify reads it on the retry shape only, clamped to 60 000 to
  1 800 000 ms; a value that is not a number is dropped and logged, and the
  step retries on the flat delay.
- It is one attempt of EVENT_STEP_MAX_ATTEMPTS and degrades the run like any
  retry. A run paused or cancelled in the wait is not picked up.
- Every step.retry log line now carries retryAt (and retryAfterMs when
  hinted); the run console reads "retrying at 20:10".
- MODULE_API 1.15.0, additions only; the engagement manifest is regenerated.
  Module-uo (^1.10.0) answers none of it and is unaffected.
- Test harness: resourcesDb.finishedRunsWithNothingLeft was never stubbed in
  eventRunner.test.js, so a tick that reached the cleanup sweep with a
  terminal run waited on the dead pool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-15 02:39:43 -05:00
a981b2b854 Merge pull request 'feat(events): a param that holds several values (MODULE_API 1.14.0)' (#213) from feat/event-param-multiple into main
Some checks failed
sync-project-tree / sync (push) Successful in 29s
Build container images / build (push) Successful in -18s
SonarQube / analysis (push) Successful in 17m58s
Build container images / deploy (push) Has been cancelled
Reviewed-on: #213
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-13 01:03:19 +00:00
743f1bc8b2 feat(events): a param that holds several values (MODULE_API 1.14.0)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 47s
PR Checks / client-build (pull_request) Successful in 48s
PR Checks / server-tests (pull_request) Successful in 13m55s
Rust PLAN_REDESIGNS §12.3 (D344): an event action param may declare
`multiple: true`, with an optional `max` (1-100, 20 when left out), on a
`string` or `int` param. The permission reward steps take several permissions
in one step through it.

- registries: the flag and `max` are checked at registration and served in the
  catalog; refused on other types, on a duration, on a template's inputs, and
  with a list as the example. A param that does not say it reads back exactly
  as declared, without the keys.
- spec: the value is a JSON array, each item the type's literal, duplicates
  dropped in order, at most `max`; a bare value is a list of one; `required`
  means at least one; an empty item is refused.
- authorize: a multiple param's example is priced as a list of one, since
  cost() is handed the array.
- editor: a multi-select. Chips above a grouped, filterable checklist for a
  source that answered; the typeahead for a searchable source; a tag box
  otherwise. Ticking past max is refused in words. A new step holds the
  example as a list of one.
- run console: params read as `name: value`, a list joined by commas.
- MODULE_API 1.13.0 -> 1.14.0 on both halves; engagement-triggers.json and the
  catalog's OpenAPI note regenerated.

Additive only: Module-uo (`^1.10.0`) declares no multiple param.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-12 15:40:30 -05:00
ca7ec53b14 Merge pull request 'feat(events): the step editor reworked, and event templates (MODULE_API 1.13.0)' (#212) from feat/event-step-editor into main
Some checks failed
sync-project-tree / sync (push) Successful in 21s
Build container images / build (push) Successful in 1m50s
SonarQube / analysis (push) Successful in 17m50s
Build container images / deploy (push) Has been cancelled
Reviewed-on: #212
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-12 09:53:42 +00:00