Phase 862 (v0.8.829): multi-guild scale + DM-during-heal composite
Add runGuildMeshMultiGuildScaleDmCompositeJourney combining Phase 861 topic isolation with Phase 860 scale roster and DM-during-heal on the primary guild. 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 862 (v0.8.829):** `runGuildMeshMultiGuildScaleDmCompositeJourney` (multi-guild scale + DM-during-heal composite). Bundle: `npm run test:ci-phase862`.
|
||||
|
||||
**Phase 861 (v0.8.828):** `runGuildMeshMultiGuildQuadChurnMatrixJourney` (P654-24 multi-guild quad churn matrix). Bundle: `npm run test:ci-phase861`.
|
||||
|
||||
**Phase 860 (v0.8.827):** `runGuildMeshQuadUltimateRosterScaleDmHealJourney` (4-peer scale heal + DM during heal). Bundle: `npm run test:ci-phase860`.
|
||||
|
||||
@@ -294,6 +294,20 @@ function matchView (view, spec) {
|
||||
if (peers > 0 && peers < 2) return false
|
||||
continue
|
||||
}
|
||||
if (key === 'meshMultiGuildScaleDmCompositeReady') {
|
||||
if (!expected) return true
|
||||
if (!!view?.partitionHealInProgress) return false
|
||||
if (!view?.lastPartitionHeal?.at) return false
|
||||
const rail = view?.guildSyncHealthRail || {}
|
||||
if (!Object.keys(rail).length && view?.mode !== 'guild') return false
|
||||
const peers = Number(view?.stats?.peers) || 0
|
||||
if (peers > 0 && peers < 2) return false
|
||||
const rosterN = (view?.members || []).length
|
||||
if (rosterN < 4 && view?.mode === 'guild') return false
|
||||
const outbox = Number(view?.guildSyncHealth?.pendingGossipCount) || 0
|
||||
if (outbox > 12) return false
|
||||
continue
|
||||
}
|
||||
if (key === 'meshQuadUltimateRosterScaleDmHealReady') {
|
||||
if (!expected) return true
|
||||
const peers = Number(view?.stats?.peers) || 0
|
||||
|
||||
+239
@@ -8814,6 +8814,245 @@ class HeadlessSession {
|
||||
}
|
||||
}
|
||||
|
||||
/** Phase 862: multi-guild scale roster + DM-during-heal composite (861 topic isolation + 860 quad scale DM). */
|
||||
async runGuildMeshMultiGuildScaleDmCompositeJourney () {
|
||||
const {
|
||||
connectGuildMeshQuad,
|
||||
disconnectGuildMeshQuadOnePeer,
|
||||
reconnectGuildMeshQuadOnePeer
|
||||
} = require('./mesh-helpers')
|
||||
const contractMode = process.env.PEARCORD_SKIP_MESH_ROUNDTRIP === '1'
|
||||
const liveQuad =
|
||||
process.env.PEARCORD_PHASE654_HEAL_FULL === '1' &&
|
||||
process.env.PEARCORD_SKIP_MESH_ROUNDTRIP !== '1'
|
||||
const secondaryCount = Math.max(
|
||||
2,
|
||||
Number(process.env.PEARCORD_PHASE862_SECONDARY_GUILDS) || 2
|
||||
)
|
||||
const memberCount = contractMode
|
||||
? Math.max(
|
||||
100,
|
||||
Number(process.env.PEARCORD_PHASE862_MEMBER_COUNT) || 120
|
||||
)
|
||||
: Math.max(
|
||||
280,
|
||||
Number(process.env.PEARCORD_PHASE862_MEMBER_COUNT) ||
|
||||
Number(process.env.PEARCORD_PHASE860_MEMBER_COUNT) ||
|
||||
300
|
||||
)
|
||||
const membersMin = contractMode
|
||||
? Math.max(80, memberCount - 20)
|
||||
: Math.max(280, memberCount - 20)
|
||||
const waitMs = memberCount >= 280 ? 150000 : 90000
|
||||
const hostLabel = 'MG Scale DM Host 862'
|
||||
const primaryToken = `mg862pri_${Date.now()}`
|
||||
const dmToken = `mg862dm_${Date.now()}`
|
||||
const churnToken = `mg862churn_${Date.now()}`
|
||||
const base = path.join(os.tmpdir(), `pearcord-agentctl-mg862-${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') })
|
||||
const peerSessions = []
|
||||
const secondaryIds = []
|
||||
const secondaryTokens = []
|
||||
const assertNoCrossLeak = async (gid, selfIdx, allTokens) => {
|
||||
await host.loadGuild(gid)
|
||||
await host.wait({ mode: 'guild' }, 45000)
|
||||
const v = await host.refreshView()
|
||||
for (let j = 0; j < allTokens.length; j++) {
|
||||
if (allTokens[j].gid === gid) continue
|
||||
const leak = (v.messages || []).some((m) =>
|
||||
String(m?.content || m?.body || '').includes(allTokens[j].token)
|
||||
)
|
||||
if (leak) {
|
||||
throw new Error(`cross-topic leak: guild ${gid} saw ${allTokens[j].token}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
await host.start()
|
||||
try {
|
||||
await host.registerUser({ username: 'mg862host', displayName: hostLabel })
|
||||
await host.createGuild({
|
||||
name: 'MG862 Primary Scale',
|
||||
publicListing: false
|
||||
})
|
||||
await host.wait({ mode: 'guild' }, 45000)
|
||||
const primaryGid = host.platform.guild?.guild?.id
|
||||
if (!primaryGid) throw new Error('primary guild id missing')
|
||||
const allTokens = [{ gid: primaryGid, token: primaryToken }]
|
||||
await host.sendGuildMessage(primaryToken)
|
||||
for (let i = 0; i < secondaryCount; i++) {
|
||||
await host.createGuild({
|
||||
name: `MG862 Secondary ${i}`,
|
||||
publicListing: false
|
||||
})
|
||||
await host.wait({ mode: 'guild' }, 45000)
|
||||
const sid = host.platform.guild?.guild?.id
|
||||
if (!sid) throw new Error(`secondary guild ${i} id missing`)
|
||||
secondaryIds.push(sid)
|
||||
const tok = `mg862sec${i}_${Date.now()}`
|
||||
secondaryTokens.push(tok)
|
||||
allTokens.push({ gid: sid, token: tok })
|
||||
await host.sendGuildMessage(tok)
|
||||
}
|
||||
const multi0 = host.exportMultiGuildSyncDiagnostics()
|
||||
const listed = multi0?.guilds || multi0?.entries || []
|
||||
if ((Array.isArray(listed) ? listed.length : 0) < secondaryCount + 1) {
|
||||
throw new Error('multi-guild diagnostics missing guild rows')
|
||||
}
|
||||
for (let i = 0; i < allTokens.length; i++) {
|
||||
await assertNoCrossLeak(allTokens[i].gid, i, allTokens)
|
||||
}
|
||||
await host.loadGuild(primaryGid)
|
||||
await host.wait({ mode: 'guild' }, 45000)
|
||||
await host.runLargeGuildMemberJourney(primaryGid, memberCount)
|
||||
const healRoster = await host.platform.runPartitionHealSync({
|
||||
force: true,
|
||||
source: 'agentctl-mg862-roster'
|
||||
})
|
||||
if (healRoster?.throttled) throw new Error('roster partition heal throttled')
|
||||
if (!contractMode) {
|
||||
await guest1.start()
|
||||
await guest2.start()
|
||||
await guest3.start()
|
||||
peerSessions.push(guest1, guest2, guest3)
|
||||
const invite = await host.platform.createInvite().catch(() => null)
|
||||
const code = invite?.shareCode || invite?.code
|
||||
if (!code) throw new Error('primary invite missing')
|
||||
await guest1.registerUser({
|
||||
username: 'mg862guesta',
|
||||
displayName: 'MG862 Guest a'
|
||||
})
|
||||
await guest2.registerUser({
|
||||
username: 'mg862guestb',
|
||||
displayName: 'MG862 Guest b'
|
||||
})
|
||||
await guest3.registerUser({
|
||||
username: 'mg862guestc',
|
||||
displayName: 'MG862 Guest c'
|
||||
})
|
||||
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 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)
|
||||
for (const sess of [host, guest1, guest2, guest3]) {
|
||||
if (typeof sess.platform.requestGuildMemberPage === 'function') {
|
||||
for (const off of [0, 100, 200]) {
|
||||
sess.platform.requestGuildMemberPage(off, { immediate: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
const g2pre = await guest2.refreshView()
|
||||
const guest2UserId = g2pre.user?.id
|
||||
if (!guest2UserId) throw new Error('guest2 user id missing')
|
||||
const healDmPromise = host.platform.runPartitionHealSync({
|
||||
force: true,
|
||||
source: 'agentctl-mg862-dm'
|
||||
})
|
||||
for (const sid of secondaryIds) {
|
||||
await assertNoCrossLeak(sid, -1, allTokens)
|
||||
}
|
||||
await host.openDmWithPeer(guest2UserId, 'MG862 DM Peer')
|
||||
const vDm = await host.refreshView()
|
||||
if (vDm.mode !== 'dm') throw new Error('host not in dm during heal')
|
||||
await host.sendDmMessage(dmToken)
|
||||
const healDm = await healDmPromise
|
||||
if (healDm?.throttled) throw new Error('dm partition heal throttled')
|
||||
await host.ipc({ type: 'select-guild', guildId: primaryGid })
|
||||
await host.wait({ mode: 'guild', activeChannelId: chId }, 45000)
|
||||
for (const sid of secondaryIds) {
|
||||
await assertNoCrossLeak(sid, -1, allTokens)
|
||||
}
|
||||
const dmSurvWait = { meshQuadDmDuringHealSurvivorReady: true }
|
||||
await guest1.wait(dmSurvWait, 30000)
|
||||
await guest3.wait(dmSurvWait, 30000)
|
||||
if (liveQuad) {
|
||||
await disconnectGuildMeshQuadOnePeer(
|
||||
host.platform,
|
||||
guest1.platform,
|
||||
guest2.platform,
|
||||
guest3.platform,
|
||||
2
|
||||
)
|
||||
await guest2.wait({ meshPeersExact: 0 }, 30000)
|
||||
const healChurn = await host.platform.runPartitionHealSync({
|
||||
force: true,
|
||||
source: 'agentctl-mg862-churn'
|
||||
})
|
||||
if (healChurn?.throttled) throw new Error('churn heal throttled')
|
||||
await reconnectGuildMeshQuadOnePeer(
|
||||
host.platform,
|
||||
guest1.platform,
|
||||
guest2.platform,
|
||||
guest3.platform,
|
||||
2
|
||||
)
|
||||
}
|
||||
await host.sendGuildMessage(churnToken)
|
||||
const churnWait = { activeChannelMessageIncludes: churnToken }
|
||||
await guest1.wait(churnWait, 90000)
|
||||
await guest3.wait(churnWait, 90000)
|
||||
}
|
||||
const healFinal = await host.platform.runPartitionHealSync({
|
||||
force: true,
|
||||
source: 'agentctl-mg862-final'
|
||||
})
|
||||
if (healFinal?.throttled) throw new Error('final partition heal throttled')
|
||||
for (const sid of secondaryIds) {
|
||||
await assertNoCrossLeak(sid, -1, allTokens)
|
||||
}
|
||||
await host.loadGuild(primaryGid)
|
||||
const compositeWait = {
|
||||
meshMultiGuildScaleDmCompositeReady: true,
|
||||
meshMultiGuildQuadChurnMatrixReady: true,
|
||||
partitionHealComplete: true,
|
||||
membersMin
|
||||
}
|
||||
await host.wait(compositeWait, waitMs)
|
||||
const multiFinal = host.exportMultiGuildSyncDiagnostics()
|
||||
const diag = host.platform.exportMeshReplicationDiagnostics(primaryGid)
|
||||
return {
|
||||
primaryGid,
|
||||
secondaryIds,
|
||||
secondaryCount,
|
||||
memberCount,
|
||||
membersMin,
|
||||
contractMode,
|
||||
liveQuad,
|
||||
primaryToken,
|
||||
dmToken,
|
||||
churnToken,
|
||||
multiFinal,
|
||||
topic: diag?.guildTopicPool?.topic || diag?.guildTopic || ''
|
||||
}
|
||||
} finally {
|
||||
for (const sess of peerSessions) {
|
||||
await sess.close().catch(() => null)
|
||||
}
|
||||
await host.close()
|
||||
}
|
||||
}
|
||||
|
||||
/** Phase 861: multi-guild 4×quad churn matrix — topic isolation + P654-24 (live when PEARCORD_PHASE654_HEAL_FULL=1). */
|
||||
async runGuildMeshMultiGuildQuadChurnMatrixJourney () {
|
||||
const {
|
||||
|
||||
Reference in New Issue
Block a user