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
This commit is contained in:
2026-10-16 15:18:53 -05:00
parent 0f29f0acb2
commit 8c30cbbddd
6 changed files with 90 additions and 12 deletions

View File

@@ -240,6 +240,44 @@ test('a step authored against an older action version is a WARNING, not an error
assert.match(report.findings[0].message, /authored against version 1/)
})
test('a module’s advice is a WARNING finding per line, and the dry run still passes (MODULE_API 1.16.0)', async () => {
// Rust PLAN_REDESIGNS §11.4, D391: a raid base's spot is checked against the
// world as it stands now. A player standing on it may walk off before the run,
// so the check is advice, never a reason the plan fails.
register([
recorder('test.spawn', {
answer: { ok: true, detail: { warnings: ['the spot is refused now: a player is within 50 m', ' ', 'at the spot now: Rock is too large', 7] } },
}),
])
const report = await verifySpec(spec([step('test.spawn')]), { user: ADMIN })
assert.equal(report.ok, true, 'advice must not fail the dry run')
assert.deepEqual(
report.findings.map((f) => [f.level, f.code, f.message, f.actionId]),
[
['warning', 'advice', 'the spot is refused now: a player is within 50 m', 'test.spawn'],
['warning', 'advice', 'at the spot now: Rock is too large', 'test.spawn'],
],
)
})
test('advice is bounded: at most ten lines, each cut at 500 characters, and anything but strings ignored', async () => {
const { adviceOf } = require('../src/events/verify')
assert.deepEqual(adviceOf(null), [])
assert.deepEqual(adviceOf({ warnings: 'one line' }), [])
assert.deepEqual(adviceOf({ note: 'no warnings here' }), [])
assert.equal(adviceOf({ warnings: Array.from({ length: 30 }, (_, i) => `line ${i}`) }).length, 10)
const long = adviceOf({ warnings: ['x'.repeat(900)] })[0]
assert.equal(long.length, 500)
assert.ok(long.endsWith('…'))
})
test('a refused step carries no advice: the refusal is the finding', async () => {
register([recorder('test.spawn', { answer: { ok: false, retry: false, error: 'no such base', detail: { warnings: ['ignored'] } } })])
const report = await verifySpec(spec([step('test.spawn')]), { user: ADMIN })
assert.equal(report.ok, false)
assert.deepEqual(report.findings.map((f) => f.code), ['refused'])
})
test('a finding says which phase and which step it is about', async () => {
// A twelve-step definition needs the finding anchored, or the author is left
// reading the message and counting rows.