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
This commit is contained in:
2026-10-22 00:38:34 -05:00
parent 9a1654fc54
commit f40dd765c3
18 changed files with 632 additions and 210 deletions

View File

@@ -1497,11 +1497,16 @@ export default function EventEditor() {
setEvent(result.event)
setVersions(await api.admin.listEventVersions(id).then((r) => r.versions || []))
// The re-pin count is said out loud, because an editor who does not know
// their fix reached next Friday finds out on Friday.
// their fix reached next Friday finds out on Friday. So is a dry run that
// carried over from the draft (W5): it is why no second one is asked for.
setNotice(
result.repinned
? `Published as v${result.version}. ${result.repinned} scheduled occurrence${result.repinned === 1 ? '' : 's'} moved to it.`
: `Published as v${result.version}.`,
[
`Published as v${result.version}.`,
result.verified ? 'The dry run that passed on this draft counts for it.' : '',
result.repinned
? `${result.repinned} scheduled occurrence${result.repinned === 1 ? '' : 's'} moved to it.`
: '',
].filter(Boolean).join(' '),
)
} catch (err) {
setProblems(err.body?.errors || [err.message])
@@ -1674,6 +1679,7 @@ export default function EventEditor() {
report that did not say would be read as the other one. */}
{report.target === 'version' ? `published v${report.version}` : 'the working draft'}
{report.recorded && ' · recorded, so scheduled occurrences may now start'}
{report.draftRecorded && ' · kept, so publishing this draft unchanged makes a verified version'}
</span>
</p>
@@ -2019,7 +2025,7 @@ export default function EventEditor() {
</div>
</div>
<p className="sans dim" style={{ fontSize: '0.76rem', margin: '8px 0 0' }}>
The key is what the run console groups by and what &ldquo;phase 3 has not started&rdquo; names, so it
The key is what the run console groups by and what &ldquo;Phase p3: steps done, waiting to advance&rdquo; names, so it
cannot change once runs exist.
</p>

View File

@@ -132,7 +132,9 @@ function GateRow({ gate, current }) {
<div style={{ padding: '8px 0', borderTop: '1px solid var(--rule)' }}>
<div className="sans" style={{ fontSize: '0.86rem', color: colour }}>
Phase <strong>{gate.phase}</strong>
{current && !gate.satisfied ? ' has not started' : ''}
{/* W2: the phase is running and its steps are done; it is the advance
that waits, so "has not started" was the wrong sentence. */}
{current && !gate.satisfied ? ': steps done, waiting to advance' : ''}
{gate.satisfied && ` — released ${gate.satisfiedBy === 'forced' ? 'by hand' : `on its ${gate.satisfiedBy === 'elapsed' ? 'deadline' : 'condition'}`}`}
{gate.stalled && ' — STALLED'}
</div>
@@ -389,7 +391,7 @@ export default function EventRun() {
</p>
)}
{/* ── Why this phase has not started (Phase 5) ──
{/* ── Why this phase has not advanced (Phase 5) ──
Above the step list for the same reason the parked cue is: a phase
waiting on a condition is `running` and looks completely healthy, and
the one screen an operator opens to find out why nothing is happening
@@ -400,7 +402,7 @@ export default function EventRun() {
style={{ padding: 14, marginBottom: 14, borderLeft: `3px solid ${waiting ? (waiting.stalled ? '#d98b84' : '#d9c184') : 'var(--rule)'}` }}
>
<h3 className="sans" style={{ margin: '0 0 4px', fontSize: '0.92rem' }}>
{waiting ? 'Why this phase has not started' : 'Phase advance conditions'}
{waiting ? 'Why this phase has not advanced' : 'Phase advance conditions'}
</h3>
<p className="sans dim" style={{ margin: '0 0 4px', fontSize: '0.8rem' }}>
{waiting ? (