General Actions to handlers Refacto

This commit is contained in:
STEINNI
2026-06-20 18:50:26 +00:00
parent 7435d96135
commit 44a84c64ec
56 changed files with 832 additions and 973 deletions
+54 -28
View File
@@ -1,17 +1,35 @@
import { MySQLClient } from '@p42/p42modules'
import { SimRepository } from '../../SimMaestro/simRepository.js'
import { SimRepository } from '../../Maestro/simRepository.js'
function agentHashKey(template, agentId) {
return(template.replace(/\[UID\]/g, agentId))
}
function parseJsonField(raw) {
function hashFieldValue(raw) {
if(raw == null) return(null)
if(typeof(raw) === 'object') return(raw)
if(typeof(raw) !== 'string') return(raw)
try { return(JSON.parse(raw)) }
catch { return(null) }
catch { return(raw) }
}
function valuesEqual(a, b) {
if(a === b) return(true)
if(a == null || b == null) return(false)
if(typeof(a) !== 'object' && typeof(b) !== 'object') return(a == b)
if(typeof(a) !== typeof(b)) return(false)
if(typeof(a) !== 'object') return(false)
if(Array.isArray(a) !== Array.isArray(b)) return(false)
const keysA = Object.keys(a)
const keysB = Object.keys(b)
if(keysA.length !== keysB.length) return(false)
for(const key of keysA) {
if(!valuesEqual(a[key], b[key])) return(false)
}
return(true)
}
const RESERVED_HASH_FIELDS = new Set(['position', 'vector', 'speed', 'segment'])
function vectorsEqual(a, b) {
for(const axis of ['x', 'y', 'z']) {
if(a[axis] !== b[axis]) return(false)
@@ -23,13 +41,12 @@ async function findSimulationFixture(ctx) {
const { guiDatabase, simDatabase } = ctx.databases
const rows = await MySQLClient.poolExecute(ctx.db, `
SELECT u.usr_uuid AS user_uuid,
BIN_TO_UUID(s.sim_uuid) AS simulation_uuid,
BIN_TO_UUID(s.sim_root_kf_uuid) AS keyframe_id
BIN_TO_UUID(s.sim_uuid) AS simulation_uuid
FROM \`${guiDatabase}\`.users u
INNER JOIN \`${guiDatabase}\`.simowners o ON o.own_usr_id = u.usr_id
INNER JOIN \`${simDatabase}\`.simulations s ON o.own_sim_uuid = s.sim_uuid
INNER JOIN \`${simDatabase}\`.edited_kf_store ekfs ON ekfs.ekfs_ekf_uuid = s.sim_root_kf_uuid
GROUP BY u.usr_uuid, s.sim_uuid, s.sim_root_kf_uuid
GROUP BY u.usr_uuid, s.sim_uuid
HAVING COUNT(ekfs.ekfs_agent_id) > 0
LIMIT 1
`)
@@ -37,14 +54,13 @@ async function findSimulationFixture(ctx) {
if(!rows.length) {
throw(new Error(
'No simulation fixture found in MySQL (need user-owned sim with root keyframe agents). '
+ 'Pass --userUuid, --simulationUuid, --keyframeId explicitly, or use --guiDatabase / --simDatabase for a test DB.'
+ 'Pass --userUuid, --simulationUuid explicitly, or use --guiDatabase / --simDatabase for a test DB.'
))
}
return({
userUuid: rows[0].user_uuid,
simulationUuid: rows[0].simulation_uuid,
keyframeId: rows[0].keyframe_id,
})
}
@@ -71,15 +87,11 @@ function waitForLifecycleEvent(ctx, eventType, timeoutMs) {
export function configureYargs(yargsBuilder) {
return(yargsBuilder.options({
userUuid: {
describe: 'User UUID to send STARTSIMULATION as (auto-discovered if omitted)',
describe: 'User UUID (dashed or 0x-prefixed hex; auto-discovered if omitted)',
type: 'string',
},
simulationUuid: {
describe: 'Simulation UUID (auto-discovered if omitted)',
type: 'string',
},
keyframeId: {
describe: 'Root keyframe UUID (auto-discovered if omitted)',
describe: 'Simulation UUID (dashed or 0x-prefixed hex; auto-discovered if omitted)',
type: 'string',
},
timeout: {
@@ -91,32 +103,32 @@ export function configureYargs(yargsBuilder) {
}
export async function run(ctx) {
const { log, argv, config, systemCnx, arenaCnx } = ctx
const { log, argv, config, systemCnx, arenaCnx, normalizeUuid } = ctx
const arenaStorage = config.gps?.arenaStorage ?? {
agentHashKey: 'arena:agents:[UID]',
agentsIndexKey: 'arena:agents',
}
log('action', 'Resolving simulation fixture from MySQL...')
let userUuid = argv.userUuid
let simulationUuid = argv.simulationUuid
let keyframeId = argv.keyframeId
let userUuid = argv.userUuid ? normalizeUuid(argv.userUuid) : undefined
let simulationUuid = argv.simulationUuid ? normalizeUuid(argv.simulationUuid) : undefined
if(!userUuid || !simulationUuid || !keyframeId) {
if(!userUuid || !simulationUuid) {
const fixture = await findSimulationFixture(ctx)
userUuid = userUuid ?? fixture.userUuid
simulationUuid = simulationUuid ?? fixture.simulationUuid
keyframeId = keyframeId ?? fixture.keyframeId
}
log('action', `User: ${userUuid}`)
log('action', `Simulation: ${simulationUuid}`)
log('action', `Keyframe: ${keyframeId}`)
const simRepo = new SimRepository(ctx.db, ctx.databases, false)
const access = await simRepo.validateSimulationAccess(userUuid, simulationUuid, keyframeId)
const access = await simRepo.validateSimulationAccess(userUuid, simulationUuid)
if(!access.ok) throw(new Error(`Simulation access check failed: ${access.err}`))
const keyframeId = access.sim.sim_root_kf_uuid
log('action', `Root keyframe: ${keyframeId}`)
const agentsResult = await simRepo.loadKeyframeAgents(keyframeId)
if(!agentsResult.ok) throw(new Error(`Failed to load keyframe agents: ${agentsResult.err}`))
if(!agentsResult.agents.length) throw(new Error('Keyframe has no agents with valid GPS values'))
@@ -137,7 +149,6 @@ export async function run(ctx) {
roles: ['*'],
payload: {
simulationUuid,
keyframeId,
infraId: null,
},
})
@@ -174,8 +185,8 @@ export async function run(ctx) {
const hash = await arenaCnx.redisHgetall(key)
const expected = expectedById.get(agentId)
const position = parseJsonField(hash.position)
const vector = parseJsonField(hash.vector)
const position = hashFieldValue(hash.position)
const vector = hashFieldValue(hash.vector)
if(!position || !vector) {
throw(new Error(`Agent ${agentId}: missing position or vector in arena hash ${key}`))
@@ -189,8 +200,23 @@ export async function run(ctx) {
throw(new Error(`Agent ${agentId}: vector mismatch (MySQL vs arena store)`))
}
log('success', `Agent ${agentId}: position and vector match MySQL`)
const storeExpected = expected.store ?? {}
for(const [field, expVal] of Object.entries(storeExpected)) {
const actualVal = hashFieldValue(hash[field])
if(!valuesEqual(actualVal, expVal)) {
throw(new Error(`Agent ${agentId}: store field "${field}" mismatch (MySQL vs arena store)`))
}
}
for(const field of Object.keys(hash)) {
if(RESERVED_HASH_FIELDS.has(field)) continue
if(!(field in storeExpected)) {
throw(new Error(`Agent ${agentId}: unexpected store field "${field}" in arena hash ${key}`))
}
}
log('success', `Agent ${agentId}: position, vector, and store values match MySQL`)
}
log('success', 'Arena store seeded correctly from MySQL (Maestro will stall waiting for readyToStart without GPS)')
log('success', 'Arena store seeded correctly from MySQL (Maestro will wait for agent + primordial daemon readyToStart until timeout)')
}