fix(core): the last fixes' core half (W1 W2 W5 W19)
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:
@@ -478,7 +478,7 @@ test('a resource the module no longer has becomes orphaned, never reverted', asy
|
||||
addResource({ ref: '0x2' })
|
||||
|
||||
const summary = await cleanup.reconcileModule('demo')
|
||||
assert.deepEqual(summary, { asked: 2, inForce: 1, orphaned: 1, unanswered: 0 })
|
||||
assert.deepEqual(summary, { asked: 2, inForce: 1, orphaned: 1, expired: 0, unanswered: 0 })
|
||||
assert.equal([...store.rows.values()].find((r) => r.ref === '0x2').status, 'orphaned')
|
||||
assert.equal([...store.rows.values()].find((r) => r.ref === '0x1').status, 'confirmed')
|
||||
assert.ok(store.log.some((e) => e.kind === 'resource.orphaned'))
|
||||
@@ -510,7 +510,7 @@ test('a module with no reconcile is not broken; core keeps believing its ledger'
|
||||
addStep(1, 'demo.spawn')
|
||||
addResource()
|
||||
const summary = await cleanup.reconcileModule('demo')
|
||||
assert.deepEqual(summary, { asked: 0, inForce: 0, orphaned: 0, unanswered: 1 })
|
||||
assert.deepEqual(summary, { asked: 0, inForce: 0, orphaned: 0, expired: 0, unanswered: 1 })
|
||||
assert.equal([...store.rows.values()][0].status, 'confirmed')
|
||||
})
|
||||
|
||||
@@ -532,7 +532,7 @@ test('placeholders are not asked about, because there is nothing to ask yet', as
|
||||
addStep(1, 'demo.spawn')
|
||||
addResource({ kind: resourcesDb.STEP_KIND, ref: 'a'.repeat(40), status: 'pending' })
|
||||
const summary = await cleanup.reconcileModule('demo')
|
||||
assert.deepEqual(summary, { asked: 0, inForce: 0, orphaned: 0, unanswered: 0 })
|
||||
assert.deepEqual(summary, { asked: 0, inForce: 0, orphaned: 0, expired: 0, unanswered: 0 })
|
||||
assert.equal([...store.rows.values()][0].status, 'pending')
|
||||
})
|
||||
|
||||
@@ -556,7 +556,7 @@ test('a lease row had no reconcile path at all until core.lease grew one', async
|
||||
addResource({ kind: 'override', ref: 'demo.rate', payload: { baseline: 1, applied: 3 } })
|
||||
|
||||
const summary = await cleanup.reconcileModule('demo')
|
||||
assert.deepEqual(summary, { asked: 1, inForce: 0, orphaned: 1, unanswered: 0 })
|
||||
assert.deepEqual(summary, { asked: 1, inForce: 0, orphaned: 1, expired: 0, unanswered: 0 })
|
||||
assert.equal([...store.rows.values()][0].status, 'orphaned')
|
||||
})
|
||||
|
||||
@@ -566,7 +566,7 @@ test('a lease the shard still has a record of stays put', async () => {
|
||||
addResource({ kind: 'override', ref: 'demo.rate', payload: { baseline: 1, applied: 3 } })
|
||||
|
||||
const summary = await cleanup.reconcileModule('demo')
|
||||
assert.deepEqual(summary, { asked: 1, inForce: 1, orphaned: 0, unanswered: 0 })
|
||||
assert.deepEqual(summary, { asked: 1, inForce: 1, orphaned: 0, expired: 0, unanswered: 0 })
|
||||
assert.equal([...store.rows.values()][0].status, 'confirmed')
|
||||
})
|
||||
|
||||
@@ -705,6 +705,61 @@ test('an expiry nobody ledgered, or a malformed one, is not an error', async ()
|
||||
assert.equal(store.log.length, 0)
|
||||
})
|
||||
|
||||
// W1 (Rust PLAN_LAST_FIXES): on a reconnect the module's reconcile can run
|
||||
// before the game's backlog reports an expiry. A row past its `until` that the
|
||||
// game no longer has is the deadline working, so it is filed expired at once
|
||||
// rather than orphaned and then expired five seconds later.
|
||||
|
||||
test('a reconcile files a row gone past its until as expired, not orphaned', async () => {
|
||||
stubExpiry()
|
||||
registerAction({ reconcile: async () => ({ ok: true, inForce: [] }) })
|
||||
addStep(1, 'demo.spawn')
|
||||
const now = new Date('2026-10-22T14:06:35Z')
|
||||
addResource({ kind: 'zone', ref: 'past', lease_until: new Date('2026-10-22T14:06:00Z') })
|
||||
addResource({ kind: 'zone', ref: 'future', lease_until: new Date('2026-10-22T14:30:00Z') })
|
||||
addResource({ kind: 'zone', ref: 'none' })
|
||||
|
||||
const summary = await cleanup.reconcileModule('demo', now)
|
||||
assert.deepEqual(summary, { asked: 3, inForce: 0, orphaned: 2, expired: 1, unanswered: 0 })
|
||||
const status = (ref) => [...store.rows.values()].find((r) => r.ref === ref).status
|
||||
assert.equal(status('past'), 'expired')
|
||||
// Before its deadline, or with none, gone is still nobody-was-looking.
|
||||
assert.equal(status('future'), 'orphaned')
|
||||
assert.equal(status('none'), 'orphaned')
|
||||
assert.deepEqual(
|
||||
store.log.filter((e) => e.detail.resource === 'zone:past').map((e) => e.kind),
|
||||
['resource.expired'],
|
||||
)
|
||||
|
||||
// The game's own word arriving afterwards finds nothing left to close.
|
||||
assert.deepEqual(await cleanup.expireResource('demo', { kind: 'zone', ref: 'past' }), { expired: 0 })
|
||||
assert.equal(store.log.filter((e) => e.detail.resource === 'zone:past').length, 1)
|
||||
})
|
||||
|
||||
test('a row past its until that the game still has stays in force', async () => {
|
||||
// A deadline is the game's promise, not core's clock: a zone the game has not
|
||||
// erased yet is still up, whatever core's time says.
|
||||
stubExpiry()
|
||||
registerAction({ reconcile: async () => ({ ok: true, inForce: ['late'] }) })
|
||||
addStep(1, 'demo.spawn')
|
||||
addResource({ kind: 'zone', ref: 'late', lease_until: new Date('2026-10-22T14:00:00Z') })
|
||||
|
||||
const summary = await cleanup.reconcileModule('demo', new Date('2026-10-22T14:06:35Z'))
|
||||
assert.equal(summary.inForce, 1)
|
||||
assert.equal([...store.rows.values()][0].status, 'confirmed')
|
||||
})
|
||||
|
||||
test('a reconcile that expires the last row of a finished run completes its cleanup', async () => {
|
||||
stubExpiry()
|
||||
registerAction({ reconcile: async () => ({ ok: true, inForce: [] }) })
|
||||
addStep(1, 'demo.spawn')
|
||||
addResource({ kind: 'zone', ref: 'x', lease_until: new Date('2026-10-22T14:00:00Z') })
|
||||
store.cleanupStatus = 'incomplete'
|
||||
|
||||
await cleanup.reconcileModule('demo', new Date('2026-10-22T14:06:35Z'))
|
||||
assert.equal(store.cleanupStatus, 'complete')
|
||||
})
|
||||
|
||||
test('the last row of a finished run expiring completes an incomplete cleanup', async () => {
|
||||
stubExpiry()
|
||||
addResource({ kind: 'zone', ref: 'x' })
|
||||
|
||||
Reference in New Issue
Block a user