From ebedee146feb1211e0e382b58f4ce9c361256a7d Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 28 Oct 2026 04:44:25 -0500 Subject: [PATCH] fix(demo): switch on the actions the seeded events use (DM28) 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 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- server/db/seedDemo.js | 32 +++++++++++++++++++++++++++++++- server/test/demoModule.test.js | 13 ++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/server/db/seedDemo.js b/server/db/seedDemo.js index 66fbd34..38cf540 100644 --- a/server/db/seedDemo.js +++ b/server/db/seedDemo.js @@ -17,6 +17,9 @@ // 5. `demo_state` — when the next wipe is due and the published logins, for // the banner and the sign-in page (§6). // +// With the events it also switches on, in the events switchboard, every action +// those events' steps use — otherwise a seeded event cannot run (§7.3). +// // A string `"$env:NAME"` anywhere in the fixture is replaced with that variable, // so the rig's sidecar token lives in the host's .env and never in the repo. An // unset variable fails the run rather than seeding an empty credential. @@ -106,6 +109,26 @@ function checkFixture(fixture) { return problems } +/** + * Every action a seeded event's steps use, once each, in first-use order. + * + * A fresh database has no switchboard rows, so every `change` and `irreversible` + * action starts disabled (EVENTS.md §K) — and a seeded event that answers "not + * enabled on this deployment" when a visitor runs it is a showcase that does not + * show (step-5 walk, W1). The seed switches on exactly these and nothing else. + */ +function eventActionIds(events = []) { + const ids = [] + for (const e of events) { + for (const phase of e?.spec?.phases || []) { + for (const step of phase?.steps || []) { + if (step?.actionId && !ids.includes(step.actionId)) ids.push(step.actionId) + } + } + } + return ids +} + // ── The run ──────────────────────────────────────────────────────────────── /* eslint-disable global-require */ @@ -174,11 +197,18 @@ async function writeContent(fixture, authorId) { if (!pub.ok) throw new Error(`event "${input.name || input.title}" did not publish: ${(pub.errors || []).join('; ')}`) } } + // Uncapped, like a switch an admin flips without filling the cap boxes: the + // deny list (DM11) is what keeps a visitor's own command step harmless, and the + // wipe takes the row away with the rest of the database. + const actionSettings = require('../src/model/events/eventActionSettings.db') + const enabled = eventActionIds(content.events) + for (const actionId of enabled) await actionSettings.put(actionId, { enabled: true, caps: {} }, authorId) log.info('content written', { posts: (content.posts || []).length, wiki: (content.wiki || []).length, pages: (content.pages || []).length, events: (content.events || []).length, + actionsEnabled: enabled, }) } @@ -251,4 +281,4 @@ if (require.main === module) { })() } -module.exports = { resolveEnv, nextResetAt, checkFixture, run } +module.exports = { resolveEnv, nextResetAt, checkFixture, eventActionIds, run } diff --git a/server/test/demoModule.test.js b/server/test/demoModule.test.js index 5998264..4de6d9d 100644 --- a/server/test/demoModule.test.js +++ b/server/test/demoModule.test.js @@ -20,7 +20,7 @@ const db = require('../src/utils/db') const registries = require('../src/modules/registries') const settingsDb = require('../src/model/settings/settings.db') const settings = require('../src/model/settings/settings.model') -const { resolveEnv, nextResetAt, checkFixture } = require('../db/seedDemo') +const { resolveEnv, nextResetAt, checkFixture, eventActionIds } = require('../db/seedDemo') after(() => db.close()) afterEach(() => { @@ -212,6 +212,17 @@ test('checkFixture accepts the four accounts and refuses anything that would lea assert.match(checkFixture({ ...GOOD, modules: [] }).join('\n'), /modules must be an object/) }) +test('eventActionIds lists each action the seeded events use, once, and the shipped fixture names all three', () => { + const step = (actionId) => ({ actionId, params: {} }) + const ev = (...phases) => ({ spec: { phases: phases.map((steps) => ({ steps: steps.map(step) })) } }) + assert.deepEqual(eventActionIds([ev(['a.run', 'core.wait'], ['a.run']), ev(['b.pay']), {}, { spec: {} }]), + ['a.run', 'core.wait', 'b.pay']) + assert.deepEqual(eventActionIds(), []) + + const fixture = require('../../deploy/demo/seed.json') // eslint-disable-line global-require + assert.deepEqual(eventActionIds(fixture.content.events), ['rust.command.run', 'rust.dt.start', 'rust.coins.pay']) +}) + test('seed:demo refuses to run off the demo', async () => { const { run } = require('../db/seedDemo') // eslint-disable-line global-require await assert.rejects(() => run('anything.json'), /only with DEMO_MODE=true/) -- 2.49.1