Phase 854: guild 4-peer resilience mega-soak journey
Add runGuildMeshQuadResilienceSoakJourney and meshQuadResilienceSoakReady matcher capstoning reconnect/heal (Phase 853) with expression soak (849). Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
Agent control plane for Pearcord — send the same IPC messages the UI sends, read `view` snapshots, and run scripted journeys against a **live worker** or an in-process **headless** session.
|
||||
|
||||
**Phase 854 (v0.8.821):** `runGuildMeshQuadResilienceSoakJourney` (4-peer resilience mega-soak). Bundle: `npm run test:ci-phase854`.
|
||||
|
||||
**Phase 853 (v0.8.820):** `runGuildMeshQuadOfflineHealJourney` (4-peer offline + partition heal). Bundle: `npm run test:ci-phase853`.
|
||||
|
||||
**Phase 852 (v0.8.819):** `runGuildMeshQuadRecoveryHealJourney` (4-peer recovery + partition heal). Bundle: `npm run test:ci-phase852`.
|
||||
|
||||
@@ -332,6 +332,28 @@ function matchView (view, spec) {
|
||||
if (outbox > 0) return false
|
||||
continue
|
||||
}
|
||||
if (key === 'meshQuadResilienceSoakReady') {
|
||||
if (!expected) return true
|
||||
const peers = Number(view?.stats?.peers) || 0
|
||||
if (peers < 2) return false
|
||||
const gid = view?.guild?.id
|
||||
const rail = gid ? view.guildSyncHealthRail?.[gid] : null
|
||||
if (rail?.meshBounded) return false
|
||||
if (!!view?.partitionHealInProgress) return false
|
||||
if (!view?.lastPartitionHeal?.at) return false
|
||||
if (view?.meshStabilityStats?.pushHalted) return false
|
||||
const hasEmoji = (view?.guildEmojis || []).some((e) => e?.name && !e.builtin)
|
||||
const hasSticker = (view?.guildStickers || []).some((s) => s?.name)
|
||||
const hasSound = (view?.guildSounds || []).some((s) => s?.name)
|
||||
if (!hasEmoji || !hasSticker || !hasSound) return false
|
||||
const chId = view?.activeChannelId
|
||||
const ch = (view?.channels || []).find((c) => c.id === chId)
|
||||
const topic = ch?.topic || view?.activeChannelSummary?.topic || ''
|
||||
if (!topic || String(topic).length < 1) return false
|
||||
const sec = Number(view?.channelSettings?.[chId]?.slowmodeSeconds) || 0
|
||||
if (sec < 1) return false
|
||||
continue
|
||||
}
|
||||
if (key === 'meshQuadExpressionSoakReady') {
|
||||
if (!expected) return true
|
||||
const peers = Number(view?.stats?.peers) || 0
|
||||
|
||||
+180
@@ -8814,6 +8814,186 @@ class HeadlessSession {
|
||||
}
|
||||
}
|
||||
|
||||
/** Phase 854: 4-peer mesh — reconnect/heal resilience then composite expression soak on all guests. */
|
||||
async runGuildMeshQuadResilienceSoakJourney () {
|
||||
const {
|
||||
connectGuildMeshQuad,
|
||||
disconnectGuildMeshQuad
|
||||
} = require('./mesh-helpers')
|
||||
const { buildTestToneWav } = require('pearcord-soundboard')
|
||||
const preToken = `res854pre_${Date.now()}`
|
||||
const msgToken = `res854msg_${Date.now()}`
|
||||
const topicToken = `res854topic_${Date.now()}`
|
||||
const emojiToken = `res854emoji_${Date.now()}`
|
||||
const stickerToken = `res854sticker_${Date.now()}`
|
||||
const soundToken = `res854sound_${Date.now()}`
|
||||
const stickerGif = Buffer.from(
|
||||
'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
|
||||
'base64'
|
||||
)
|
||||
const base = path.join(os.tmpdir(), `pearcord-agentctl-res854-${Date.now()}`)
|
||||
const host = new HeadlessSession({ storagePath: path.join(base, 'host') })
|
||||
const guest1 = new HeadlessSession({ storagePath: path.join(base, 'guest1') })
|
||||
const guest2 = new HeadlessSession({ storagePath: path.join(base, 'guest2') })
|
||||
const guest3 = new HeadlessSession({ storagePath: path.join(base, 'guest3') })
|
||||
await host.start()
|
||||
await guest1.start()
|
||||
await guest2.start()
|
||||
await guest3.start()
|
||||
try {
|
||||
await host.registerUser({ username: 'reshost854', displayName: 'Resilience Host 854' })
|
||||
await host.createGuild({ name: 'Resilience Soak Quad 854', publicListing: false })
|
||||
await host.wait({ mode: 'guild' }, 45000)
|
||||
const invite = await host.platform.createInvite().catch(() => null)
|
||||
const code = invite?.shareCode || invite?.code
|
||||
if (!code) throw new Error('host invite missing')
|
||||
await guest1.registerUser({ username: 'resguest854a', displayName: 'Resilience Guest 854a' })
|
||||
await guest2.registerUser({ username: 'resguest854b', displayName: 'Resilience Guest 854b' })
|
||||
await guest3.registerUser({ username: 'resguest854c', displayName: 'Resilience Guest 854c' })
|
||||
await guest1.ipc({ type: 'join-invite', code })
|
||||
await guest2.ipc({ type: 'join-invite', code })
|
||||
await guest3.ipc({ type: 'join-invite', code })
|
||||
await guest1.wait({ mode: 'guild' }, 45000)
|
||||
await guest2.wait({ mode: 'guild' }, 45000)
|
||||
await guest3.wait({ mode: 'guild' }, 45000)
|
||||
const chId = (await host.refreshView()).activeChannelId
|
||||
if (!chId) throw new Error('active channel missing')
|
||||
for (const sess of [guest1, guest2, guest3]) {
|
||||
await sess.ipc({ type: 'select-channel', channelId: chId })
|
||||
await sess.wait({ activeChannelId: chId }, 30000)
|
||||
}
|
||||
await connectGuildMeshQuad(
|
||||
host.platform,
|
||||
guest1.platform,
|
||||
guest2.platform,
|
||||
guest3.platform,
|
||||
55000
|
||||
)
|
||||
const quadWait = { meshQuadReady: true, membersMin: 4 }
|
||||
await host.wait(quadWait, 30000)
|
||||
await guest1.wait(quadWait, 30000)
|
||||
await guest2.wait(quadWait, 30000)
|
||||
await guest3.wait(quadWait, 30000)
|
||||
await host.sendGuildMessage(preToken)
|
||||
const preWait = { activeChannelMessageIncludes: preToken }
|
||||
await guest1.wait(preWait, 90000)
|
||||
await guest2.wait(preWait, 90000)
|
||||
await guest3.wait(preWait, 90000)
|
||||
await disconnectGuildMeshQuad(
|
||||
host.platform,
|
||||
guest1.platform,
|
||||
guest2.platform,
|
||||
guest3.platform,
|
||||
20000
|
||||
)
|
||||
for (const sess of [guest1, guest2, guest3]) {
|
||||
await sess.wait({ meshPeersExact: 0 }, 30000)
|
||||
}
|
||||
await connectGuildMeshQuad(
|
||||
host.platform,
|
||||
guest1.platform,
|
||||
guest2.platform,
|
||||
guest3.platform,
|
||||
55000
|
||||
)
|
||||
const g2v0 = await guest2.refreshView()
|
||||
const guest2UserId = g2v0.user?.id
|
||||
if (!guest2UserId) throw new Error('guest2 user id missing')
|
||||
const gid = host.platform.guild?.guild?.id
|
||||
if (!gid) throw new Error('guild id missing')
|
||||
const t0 = Date.now()
|
||||
await host.platform._ingestPresenceVectorBundle(gid, [
|
||||
{ userId: guest2UserId, status: 'dnd', at: t0 - 5000, vectorSeq: 14 }
|
||||
])
|
||||
await host.platform._ingestPresenceVectorBundle(gid, [
|
||||
{ userId: guest2UserId, status: 'online', at: t0, vectorSeq: 14 }
|
||||
])
|
||||
const healMid = await host.platform.runPartitionHealSync({
|
||||
force: true,
|
||||
source: 'agentctl-quad-res854-mid'
|
||||
})
|
||||
if (healMid?.throttled) throw new Error('mid partition heal throttled')
|
||||
for (const p of [host.platform, guest1.platform, guest2.platform, guest3.platform]) {
|
||||
if (typeof p.runOfflineRecoverySync === 'function') {
|
||||
await p.runOfflineRecoverySync().catch(() => null)
|
||||
}
|
||||
}
|
||||
await host.sendGuildMessage(msgToken)
|
||||
await host.platform.setChannelTopic(chId, `Phase 854 ${topicToken}`)
|
||||
await host.upsertGuildEmoji({ name: emojiToken, glyph: '🛡️' })
|
||||
await host.upsertGuildSticker({
|
||||
name: stickerToken,
|
||||
description: 'Mesh 854 sticker',
|
||||
image: {
|
||||
data: stickerGif,
|
||||
filename: `${stickerToken}.gif`,
|
||||
mimeType: 'image/gif'
|
||||
}
|
||||
})
|
||||
await host.upsertGuildSound({
|
||||
name: soundToken,
|
||||
description: 'Mesh 854 sound',
|
||||
audio: {
|
||||
data: buildTestToneWav(250),
|
||||
filename: `${soundToken}.wav`,
|
||||
mimeType: 'audio/wav'
|
||||
}
|
||||
})
|
||||
const slowSec = 30
|
||||
await host.platform.setChannelSlowmode(chId, slowSec)
|
||||
const healFinal = await host.platform.runPartitionHealSync({
|
||||
force: true,
|
||||
source: 'agentctl-quad-res854-final'
|
||||
})
|
||||
if (healFinal?.throttled) throw new Error('final partition heal throttled')
|
||||
for (const p of [host.platform, guest1.platform, guest2.platform, guest3.platform]) {
|
||||
if (typeof p.requestGuildSyncFanout === 'function') {
|
||||
p.requestGuildSyncFanout()
|
||||
}
|
||||
}
|
||||
const soakWait = {
|
||||
meshQuadResilienceSoakReady: true,
|
||||
meshQuadReady: true,
|
||||
membersMin: 4,
|
||||
partitionHealComplete: true,
|
||||
activeChannelMessageIncludes: msgToken,
|
||||
activeChannelTopicIncludes: topicToken,
|
||||
guildEmojiNamed: emojiToken,
|
||||
guildStickerNamed: stickerToken,
|
||||
guildSoundNamed: soundToken,
|
||||
channelSlowmodeAt: { channelId: chId, minSeconds: slowSec },
|
||||
presencePeersMin: 3
|
||||
}
|
||||
await guest1.wait(soakWait, 120000)
|
||||
await guest2.wait(soakWait, 120000)
|
||||
await guest3.wait(soakWait, 120000)
|
||||
const presWait = { peerPresenceStatus: { userId: guest2UserId, status: 'online' } }
|
||||
await host.wait(presWait, 90000)
|
||||
await guest1.wait(presWait, 90000)
|
||||
await guest3.wait(presWait, 90000)
|
||||
await guest1.wait({ activeChannelMessageIncludes: preToken }, 90000)
|
||||
const g3v = await guest3.refreshView()
|
||||
const sec3 = Number(g3v.channelSettings?.[chId]?.slowmodeSeconds) || 0
|
||||
if (sec3 < slowSec) {
|
||||
throw new Error('guest3 missing slowmode on quad resilience soak')
|
||||
}
|
||||
return {
|
||||
preToken,
|
||||
msgToken,
|
||||
topicToken,
|
||||
healMidMs: healMid?.durationMs,
|
||||
healFinalMs: healFinal?.durationMs,
|
||||
guest3Peers: g3v.stats?.peers,
|
||||
guest3Slowmode: sec3
|
||||
}
|
||||
} finally {
|
||||
await host.close()
|
||||
await guest1.close()
|
||||
await guest2.close()
|
||||
await guest3.close()
|
||||
}
|
||||
}
|
||||
|
||||
/** Phase 853: 4-peer mesh — offline recovery + partition heal after quad reconnect. */
|
||||
async runGuildMeshQuadOfflineHealJourney () {
|
||||
const {
|
||||
|
||||
Reference in New Issue
Block a user