Phase 849: runGuildMeshQuadExpressionSoakJourney and meshQuadExpressionSoakReady
Composite 4-peer soak validates Phases 843–848 expression/metadata fanout together. 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.
|
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 849 (v0.8.816):** `runGuildMeshQuadExpressionSoakJourney` (4-peer composite expression soak). Bundle: `npm run test:ci-phase849`.
|
||||||
|
|
||||||
**Phase 848 (v0.8.815):** `runGuildMeshQuadChannelLifecycleJourney` (4-peer channel create/delete). Bundle: `npm run test:ci-phase848`.
|
**Phase 848 (v0.8.815):** `runGuildMeshQuadChannelLifecycleJourney` (4-peer channel create/delete). Bundle: `npm run test:ci-phase848`.
|
||||||
|
|
||||||
**Phase 847 (v0.8.814):** `runGuildMeshQuadSoundJourney` (4-peer soundboard). Bundle: `npm run test:ci-phase847`.
|
**Phase 847 (v0.8.814):** `runGuildMeshQuadSoundJourney` (4-peer soundboard). Bundle: `npm run test:ci-phase847`.
|
||||||
|
|||||||
@@ -281,6 +281,25 @@ function matchView (view, spec) {
|
|||||||
if (!Array.isArray(view?.channels) || view.channels.length < 1) return false
|
if (!Array.isArray(view?.channels) || view.channels.length < 1) return false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if (key === 'meshQuadExpressionSoakReady') {
|
||||||
|
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
|
||||||
|
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 === 'meshQuadUnreadReady') {
|
if (key === 'meshQuadUnreadReady') {
|
||||||
if (!expected) return true
|
if (!expected) return true
|
||||||
const peers = Number(view?.stats?.peers) || 0
|
const peers = Number(view?.stats?.peers) || 0
|
||||||
|
|||||||
+116
@@ -8814,6 +8814,122 @@ class HeadlessSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Phase 849: 4-peer mesh — composite expression + channel metadata soak on all guests. */
|
||||||
|
async runGuildMeshQuadExpressionSoakJourney () {
|
||||||
|
const { connectGuildMeshQuad } = require('./mesh-helpers')
|
||||||
|
const { buildTestToneWav } = require('pearcord-soundboard')
|
||||||
|
const msgToken = `soak849msg_${Date.now()}`
|
||||||
|
const topicToken = `soak849topic_${Date.now()}`
|
||||||
|
const emojiToken = `soak849emoji_${Date.now()}`
|
||||||
|
const stickerToken = `soak849sticker_${Date.now()}`
|
||||||
|
const soundToken = `soak849sound_${Date.now()}`
|
||||||
|
const stickerGif = Buffer.from(
|
||||||
|
'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
|
||||||
|
'base64'
|
||||||
|
)
|
||||||
|
const base = path.join(os.tmpdir(), `pearcord-agentctl-expr849-${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: 'exhost849', displayName: 'Expr Host 849' })
|
||||||
|
await host.createGuild({ name: 'Expr Soak Quad 849', 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: 'exguest849a', displayName: 'Expr Guest 849a' })
|
||||||
|
await guest2.registerUser({ username: 'exguest849b', displayName: 'Expr Guest 849b' })
|
||||||
|
await guest3.registerUser({ username: 'exguest849c', displayName: 'Expr Guest 849c' })
|
||||||
|
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(msgToken)
|
||||||
|
await host.platform.setChannelTopic(chId, `Phase 849 ${topicToken}`)
|
||||||
|
await host.upsertGuildEmoji({ name: emojiToken, glyph: '🎪' })
|
||||||
|
await host.upsertGuildSticker({
|
||||||
|
name: stickerToken,
|
||||||
|
description: 'Mesh 849 sticker',
|
||||||
|
image: {
|
||||||
|
data: stickerGif,
|
||||||
|
filename: `${stickerToken}.gif`,
|
||||||
|
mimeType: 'image/gif'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await host.upsertGuildSound({
|
||||||
|
name: soundToken,
|
||||||
|
description: 'Mesh 849 sound',
|
||||||
|
audio: {
|
||||||
|
data: buildTestToneWav(250),
|
||||||
|
filename: `${soundToken}.wav`,
|
||||||
|
mimeType: 'audio/wav'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const slowSec = 30
|
||||||
|
await host.platform.setChannelSlowmode(chId, slowSec)
|
||||||
|
for (const p of [host.platform, guest1.platform, guest2.platform, guest3.platform]) {
|
||||||
|
if (typeof p.requestGuildSyncFanout === 'function') {
|
||||||
|
p.requestGuildSyncFanout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const soakWait = {
|
||||||
|
meshQuadExpressionSoakReady: true,
|
||||||
|
activeChannelMessageIncludes: msgToken,
|
||||||
|
activeChannelTopicIncludes: topicToken,
|
||||||
|
guildEmojiNamed: emojiToken,
|
||||||
|
guildStickerNamed: stickerToken,
|
||||||
|
guildSoundNamed: soundToken,
|
||||||
|
channelSlowmodeAt: { channelId: chId, minSeconds: slowSec }
|
||||||
|
}
|
||||||
|
await guest1.wait(soakWait, 120000)
|
||||||
|
await guest2.wait(soakWait, 120000)
|
||||||
|
await guest3.wait(soakWait, 120000)
|
||||||
|
const g3v = await guest3.refreshView()
|
||||||
|
const sec3 = Number(g3v.channelSettings?.[chId]?.slowmodeSeconds) || 0
|
||||||
|
if (sec3 < slowSec) {
|
||||||
|
throw new Error('guest3 missing slowmode on quad expression soak')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
msgToken,
|
||||||
|
topicToken,
|
||||||
|
emojiToken,
|
||||||
|
guest3Peers: g3v.stats?.peers,
|
||||||
|
guest3Slowmode: sec3
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await host.close()
|
||||||
|
await guest1.close()
|
||||||
|
await guest2.close()
|
||||||
|
await guest3.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Phase 829: 4-peer mesh — drop one guest, survivors stay synced, rejoin + recovery burst. */
|
/** Phase 829: 4-peer mesh — drop one guest, survivors stay synced, rejoin + recovery burst. */
|
||||||
async runGuildMeshQuadChurnRejoinJourney () {
|
async runGuildMeshQuadChurnRejoinJourney () {
|
||||||
const {
|
const {
|
||||||
|
|||||||
Reference in New Issue
Block a user