Commit Graph

515 Commits

Author SHA1 Message Date
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
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
e58857c133 feat(events): a searchable source's chosen note is found when the page lacks it
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 1m47s
PR Checks / client-build (pull_request) Successful in 1m58s
PR Checks / server-tests (pull_request) Successful in 14m58s
A searchable source answers the editor a bounded first page, so the value an
author picked (or a template filled in) may not be on it, and its note with it.
useChosenNote asks the source for that value as a search term, once per value,
and shows the exact match's note. Found building the Rust kit warning
(PLAN_REDESIGNS §2.6), whose source - rust.options.grouppermissions - is
searchable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-12 02:23:18 -05:00
3e243ce772 feat(events): the step editor reworked, and event templates (MODULE_API 1.13.0)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 2m26s
PR Checks / client-build (pull_request) Successful in 2m44s
PR Checks / server-tests (pull_request) Successful in 20m38s
Rust PLAN_REDESIGNS §2 (U-3-U-6, F11; D192, D334), core half. Game-agnostic:
Module-uo's steps get the new controls and nothing else.

Contract (MODULE_API 1.12.0 -> 1.13.0, four optional additions):
- api.registerEventTemplates([{ id, label, description, inputs, build }]):
  inputs are params (<= 8); core checks them, calls build() (10 s), validates
  the result as a spec and answers an unsaved draft.
  POST /admin/events/catalog/templates/:templateId (admin, editor).
- a param's duration: 'minutes' (int/float only); core.lease's minutes has it.
- a lease's example for its value.
- an option row's note (<= 300 chars), shown under the chosen field.

Editor:
- no type tags or bare asterisks; a required field says so in words once a
  save has been tried with it empty (and the save stops before the round trip).
- boolean: toggle when required, [Not set] [Yes] [No] when optional (D334).
- a choice of two or three is a row of buttons (lease values, small sources).
- core.lease drawn as the chosen lease: picked by label; value by the lease's
  type, range, values and example; target picker over target.source (F11);
  minutes with the lease's longest hold.
- a timed step keeps its run open: a phase with no advance rule gets
  after: <longest hold> when a timed step is added; a sentence under any phase
  whose run can end sooner (U-6).
- Start asks "Publish and start" or "Start version N" when the working copy is
  ahead of the published version (draftAhead on GET/PUT/publish; U-5).
- New event can start from a module's template.
- core's own examples name no game (§0.9).

Swagger, routes.manifest/guards and engagement-triggers.json regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-12 02:08:04 -05:00
43f706c003 Merge pull request 'fix(events): the public phase label finds the phase by key (runicnpc D314)' (#211) from fix/event-public-phase-label into main
Some checks failed
sync-project-tree / sync (push) Successful in 18s
Build container images / build (push) Successful in 1m39s
SonarQube / analysis (push) Successful in 17m24s
Build container images / deploy (push) Has been cancelled
Reviewed-on: #211
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-07 02:50:14 +00:00
ef93c73d2c fix(events): the public phase label finds the phase by key (runicnpc D314)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 36s
PR Checks / client-build (pull_request) Successful in 7m50s
PR Checks / server-tests (pull_request) Successful in 13m50s
eventPublic.phaseLabel matched a run's current_phase against p.id, but spec.js
validates and the engine writes the phase's `key`, so every live run's public
page read "Under way" since Events Phase 14a, UO's events included. It now
matches `key` first and still accepts `id` second. The test fixture moves to
`key`, as real specs are, and a unit test covers a UO-shaped spec, the id
fallback, key-before-id and an unknown phase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-06 16:57:46 -05:00
0a37a44164 Merge pull request 'feat(events): an event action's progress() on a live run's public page (MODULE_API 1.12.0)' (#210) from feat/events-action-progress into main
Some checks failed
sync-project-tree / sync (push) Successful in 26s
Build container images / build (push) Successful in 1m46s
SonarQube / analysis (push) Failing after 16m44s
Build container images / deploy (push) Has been cancelled
Reviewed-on: #210
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-10-06 12:58:52 +00:00
9ddb59a326 feat(events): an event action's progress() on a live run's public page (MODULE_API 1.12.0)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 38s
PR Checks / client-build (pull_request) Successful in 49s
PR Checks / server-tests (pull_request) Successful in 20m42s
An event action may declare an optional `progress(envelope)`: how its
step stands while the run is live. Core accepts two shapes and
interprets neither:

- a count: { label, left, of }, rendered "Bandits: 3 of 8 left";
- a percentage: { label, percent }, rendered "The Juggernaut: 62%".

How core asks:

- `events/progress.js` asks each step of a live run whose action has
  one. It passes the step's own held ledger rows and the reader's
  account id, so the module applies its own visibility.
- Each call has a 2 s deadline. A throw, a late answer or any other
  shape is a line not shown, never an error on the page.
- One answer per run and reader is cached for 5 s.
- The public event's live occurrence carries the lines as `progress`.
  The route takes an optional session for the reader.
- The page renders them under the phase label and re-reads them every
  15 s while the event is live.

Core stays game-agnostic: no game's words are in it. Additive, so a
minor bump. Module-uo declares ^1.10.0, registers no action with
progress(), and its actions and pages are unchanged.

From RunicNPC stage 8 (D304, D305).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-06 02:47:00 -05:00
f0e7d2aa2a Merge pull request 'feat(events): an expired ledger status and ctx.events.expired (MODULE_API 1.11.0)' (#209) from feat/events-expired-status into main
All checks were successful
sync-project-tree / sync (push) Successful in 17s
Build container images / build (push) Successful in -1s
Build container images / deploy (push) Successful in 39s
SonarQube / analysis (push) Successful in 17m21s
Reviewed-on: #209
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-09-27 06:10:43 +00:00
224e2b4dbc fix(events): settle a finished run whose last resource expired while it ran
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 31s
PR Checks / client-build (pull_request) Successful in 31s
PR Checks / server-tests (pull_request) Successful in 20m48s
Found walking Rust PLAN_FIXES step 2 (run 46 on the walk core): a zone expired
while its run was still going, so expireResource left the run's cleanup status
to its terminal path. When the run was cancelled the sweep never selected it,
because runsNeedingCleanup joins on an unresolved row and it had none, so
cleanup_status sat at `pending` for ever over an empty ledger.

The sweep now settles finished runs that are `pending` with no unresolved row
as `complete` (never over `incomplete`, which is a human's to clear). Checked
against the walk database: the query returns run 46 and nothing else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-26 22:19:17 -05:00
cc1f49af29 feat(events): an expired ledger status and ctx.events.expired (MODULE_API 1.11.0)
All checks were successful
PR Checks / client-build (pull_request) Successful in 35s
PR Checks / server-tests (pull_request) Successful in 5m48s
PR Checks / bot-tests (pull_request) Successful in 7m51s
A game that ends a resource at its own deadline — a Rust zone erased when its
time is up — could reach core only through ctx.events.reconcile(), which files
it `orphaned`: amber, "it vanished", and still claimable for a revert. The new
call files it as the plan working (Rust PLAN_FIXES F14, D170, D183):

- event_run_resources.status gains `expired`, terminal like `reverted`: the
  sweep never takes it back, live_marker releases the target, and it joins
  neither HELD nor UNRESOLVED. The ENUM ALTER re-runs as a no-op on every boot
  (checked on MariaDB 11.8 with the stored generated column depending on it).
- ctx.events.expired({ kind, ref }) marks the calling module's own pending,
  confirmed or orphaned rows for that target expired and logs
  `resource.expired`; a revert in flight is left to finish. The owner is bound
  by the loader, like reconcile. A finished run whose last unresolved row this
  was goes to cleanup `complete`, even from `incomplete`.
- The run console shows it green, "ended by the game on time".

Additions only, so minor. Module-uo (coreApi ^1.10.0) calls none of it and
reads no ledger status; the contract test now asserts the range still holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-26 21:13:07 -05:00
1a76b98d76 Merge pull request 'fix(modules): a site runs one module; the installer refuses a second' (#204) from fix/one-module-per-site into main
All checks were successful
sync-project-tree / sync (push) Successful in 10s
Build container images / build (push) Successful in -2s
Build container images / deploy (push) Successful in 1m46s
SonarQube / analysis (push) Successful in 9m11s
Reviewed-on: #204
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-09-26 22:28:57 +00:00
a169a463a5 Merge branch 'main' into fix/one-module-per-site
All checks were successful
PR Checks / client-build (pull_request) Successful in 33s
PR Checks / bot-tests (pull_request) Successful in 32s
PR Checks / server-tests (pull_request) Successful in 5m47s
2026-09-26 22:22:56 +00:00
90ba8cca16 Merge pull request 'feat(events): publish runId on a public calendar run entry (Rust D125)' (#208) from feat/public-calendar-run-id into main
Some checks failed
sync-project-tree / sync (push) Successful in 12s
Build container images / build (push) Successful in 1m50s
Build container images / deploy (push) Successful in 42s
SonarQube / analysis (push) Failing after 6m11s
Reviewed-on: #208
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-09-25 17:43:50 +00:00
b5616f359c feat(events): publish runId on a public calendar run entry (Rust D125)
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 39s
PR Checks / client-build (pull_request) Successful in 42s
PR Checks / server-tests (pull_request) Successful in 5m54s
A run entry on GET /public/events now names its run, the same id the
event page already publishes on each occurrence and `?run=` takes. A
Rust map marker carries core's run id and nothing else about its event,
so without this the app could only find the event by fetching every
event page.

A projected entry has no runId: nothing is committed to it. Rehearsals
and unlisted events stay absent from the calendar, so their markers
stay unlinked. The web calendar ignores the field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-25 07:38:59 -05:00
e4f088e90b fix(modules): a site runs one module; the installer refuses a second
All checks were successful
PR Checks / client-build (pull_request) Successful in 33s
PR Checks / bot-tests (pull_request) Successful in 34s
PR Checks / server-tests (pull_request) Successful in 13m28s
A site is one game, and the module contract already has singletons that
assume it. registerTeamProvider holds one value per deployment, and a
second module registering one fails that module's whole load. The loader
scans alphabetically, so installing module-rust (which gains a Team
provider in its phase 9) beside module-uo would have taken uo down, not
rust.

install() now refuses, with 409 and before the artifact is downloaded,
any install whose id differs from a module already on the volume. An
upgrade of the installed module is still accepted; to change game,
remove the module first. Both install surfaces share this path, so a
MODULES declaration naming two modules installs the first and reports
the second as refused without failing the boot.

"Installed" means what the loader would scan: a directory named with a
module id that holds a module.json. An install's scratch directory and a
swap's aside copy do not count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-23 01:26:52 -05:00
702ab89ae2 Merge pull request 'fix(admin): stop a long action name printing over the activity row beside it' (#203) from fix/activity-action-overflow into main
All checks were successful
sync-project-tree / sync (push) Successful in 13s
Build container images / build (push) Successful in 1m29s
Build container images / deploy (push) Successful in 46s
SonarQube / analysis (push) Successful in 9m11s
Reviewed-on: #203
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-09-21 22:24:29 +00:00
9e1f591b25 fix(admin): stop a long action name printing over the activity row beside it
All checks were successful
PR Checks / client-build (pull_request) Successful in 57s
PR Checks / server-tests (pull_request) Successful in 6m9s
PR Checks / bot-tests (pull_request) Successful in 8m3s
The dashboard renders an activity row action in a fixed `width: 110` span with
`flex: none` and no overflow handling, so a name wider than that overflows its
box and prints on top of the detail text next to it.

Core own actions all fit. A module one need not: `module-rust` writes
`rust.account.unlink.staff` when staff sever a player Steam link, and it
overlapped `steamId: …` on a live dashboard. `module-uo` `uoLink.account.link`
is already close to the edge.

`minWidth` instead of `width` keeps the column aligned for every short name and
lets a longer one push the detail right rather than sit under it. One property,
verified in a browser with both rows on screen.

Found while walking module-rust phase 6; raised here rather than worked around
there, because a module may legitimately name an action and shortening one
module names only moves the ceiling to the next one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM
2026-09-21 09:10:43 -05:00
efa9db7330 Merge pull request 'chore(tools): delete the cliloc converter the Asset Bridge replaced (Asset Bridge cutover, 2 of 5)' (#202) from edge into main
All checks were successful
sync-project-tree / sync (push) Successful in 34s
Build container images / build (push) Successful in 22s
Build container images / deploy (push) Successful in 38s
SonarQube / analysis (push) Successful in 9m11s
Reviewed-on: #202
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-09-15 10:20:24 +00:00
720103e3d4 Merge branch 'main' into edge
All checks were successful
PR Checks / client-build (pull_request) Successful in 30s
PR Checks / bot-tests (pull_request) Successful in 31s
PR Checks / server-tests (pull_request) Successful in 13m24s
2026-09-15 10:04:54 +00:00