Mitra availability: read paths respect require_mitra_ping=false
When the operator sets require_mitra_ping=false, the auto-offline sweep early-returns (by design — "don't gate online status on heartbeat freshness"). The three Valkey read paths still gated on heartbeat freshness anyway, which trapped the system: sweep won't remove the mitra from mitras:online, but readers reject them as stale. The customer CTA stayed permanently disabled with no recovery. Fix all three to skip the heartbeat-freshness check when require_ping is off, matching the sweep's contract: - computeAvailabilityFromValkey (customer beacon) - isMitraReachable (extension service) - findAvailableMitrasFromValkey (pairing candidate finder) The Postgres fallbacks already did the right thing (is_online only, no heartbeat compare); this aligns the Valkey hot path. Also: PATCH /internal/config/mitra-ping now publishes config:invalidate for require_mitra_ping and mitra_stale_after_seconds, and the subscriber in mitra-status.service was widened to listen for both. Flipping the toggle in CC now busts the 10s availability snapshot immediately instead of waiting out the TTL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -224,6 +224,27 @@ describe('mitra-status valkey mirror', () => {
|
||||
|
||||
expect(await isMitraReachable(m.id)).toBe(false)
|
||||
})
|
||||
|
||||
// Mirrors the autoOfflineStaleMitras "no-op when require_ping=false"
|
||||
// contract: read paths must not gate on heartbeat when sweep doesn't.
|
||||
it('returns true with stale heartbeat when require_ping=false', async () => {
|
||||
const sql = db()
|
||||
try {
|
||||
await sql`
|
||||
UPDATE app_config SET value=${sql.json({ value: false })}
|
||||
WHERE key='require_mitra_ping'
|
||||
`
|
||||
const m = await createMitra({ callName: 'NoPing', isOnline: true })
|
||||
await v().set(vkHeartbeatKey(m.id), new Date(Date.now() - 3_600_000).toISOString())
|
||||
|
||||
expect(await isMitraReachable(m.id)).toBe(true)
|
||||
} finally {
|
||||
await sql`
|
||||
UPDATE app_config SET value=${sql.json({ value: true })}
|
||||
WHERE key='require_mitra_ping'
|
||||
`
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// ---------- recomputeCapacity ----------
|
||||
@@ -331,6 +352,35 @@ describe('mitra-status valkey mirror', () => {
|
||||
await invalidateAvailabilityCache()
|
||||
expect(await v().get('availability:snapshot')).toBeNull()
|
||||
})
|
||||
|
||||
// Regression: when an operator turns off the ping requirement, the
|
||||
// auto-offline sweep is also disabled, so heartbeats may legitimately
|
||||
// become arbitrarily old. The beacon must NOT filter those out — that
|
||||
// would put the CTA in a permanently-disabled state with no recovery
|
||||
// path (sweep won't remove the mitra; cache always re-computes false).
|
||||
it('includes mitras with stale heartbeats when require_ping=false', async () => {
|
||||
const sql = db()
|
||||
try {
|
||||
await sql`
|
||||
UPDATE app_config SET value=${sql.json({ value: false })}
|
||||
WHERE key='require_mitra_ping'
|
||||
`
|
||||
const m = await createMitra({ callName: 'NoPingRequired', isOnline: true })
|
||||
// Heartbeat 1 hour old — well past any reasonable stale_after_seconds.
|
||||
await v().set(vkHeartbeatKey(m.id), new Date(Date.now() - 3_600_000).toISOString())
|
||||
await v().del('availability:snapshot')
|
||||
|
||||
const result = await countAvailableMitrasFromCache()
|
||||
expect(result.available).toBe(true)
|
||||
expect(result.count).toBe(1)
|
||||
} finally {
|
||||
await sql`
|
||||
UPDATE app_config SET value=${sql.json({ value: true })}
|
||||
WHERE key='require_mitra_ping'
|
||||
`
|
||||
await v().del('availability:snapshot')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// ---------- autoOfflineStaleMitras ----------
|
||||
|
||||
Reference in New Issue
Block a user