test(qa): resolve Matrix target-both approvals via gateway

This commit is contained in:
Vincent Koc
2026-05-03 18:29:15 -07:00
parent df39e611f8
commit fcfb6500da
2 changed files with 32 additions and 16 deletions

View File

@@ -81,6 +81,7 @@ Docs: https://docs.openclaw.ai
- QA/Slack: fail the live mention-gating scenario on any unexpected SUT reply, even when the reply does not echo the expected marker. Thanks @vincentkoc.
- QA/Matrix: steer the live tool-progress preview check away from `HEARTBEAT.md` and report final preview candidates when the live marker reply misses the exact token. Thanks @vincentkoc.
- QA/Matrix: let the live tool-progress preview and error checks verify progress replacement events without depending on the preview saying `Working`, `tool: read`, an unlabelled/pathless `read from`, or the original draft root being observed. Thanks @vincentkoc.
- QA/Matrix: keep the target=both approval scenario focused on channel and DM metadata delivery by resolving the accepted approval through the gateway after both Matrix events are observed. Thanks @vincentkoc.
- QA/Matrix: wait for live approval reactions to echo before starting the threaded approval decision timeout. Thanks @vincentkoc.
- QA/Matrix: reuse the primed driver sync stream when confirming approval reaction echoes, avoiding missed self-reactions in live release runs. Thanks @vincentkoc.
- Tlon: expose `groupInviteAllowlist` in the channel config schema and clarify that group invite auto-accept fails closed without an invite allowlist. Thanks @vincentkoc.

View File

@@ -224,6 +224,14 @@ function assertApprovalDecisionResult(params: {
}
}
function assertApprovalResolveResult(result: unknown) {
const resolved =
typeof result === "object" && result !== null ? (result as { ok?: unknown }) : null;
if (resolved?.ok !== true) {
throw new Error(`approval resolve result was ${formatApprovalResultValue(result)}`);
}
}
function formatApprovalResultValue(value: unknown) {
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
return String(value);
@@ -305,6 +313,24 @@ async function waitForApprovalDecision(params: {
);
}
async function resolveApprovalDecision(params: {
approvalId: string;
context: MatrixQaScenarioContext;
decision: MatrixQaApprovalDecision;
kind: MatrixQaApprovalKind;
}) {
const gatewayCall = requireMatrixQaGatewayCall(params.context);
const method = params.kind === "exec" ? "exec.approval.resolve" : "plugin.approval.resolve";
return await gatewayCall(
method,
{ decision: params.decision, id: params.approvalId },
{
expectFinal: true,
timeoutMs: MATRIX_QA_APPROVAL_DECISION_TIMEOUT_MS + 5_000,
},
);
}
function readAcceptedApprovalRequest(result: unknown) {
const accepted =
typeof result === "object" && result !== null
@@ -563,22 +589,13 @@ export async function runApprovalChannelTargetBothScenario(context: MatrixQaScen
if (channelApproval.event.approval?.id !== dmApproval.event.approval?.id) {
throw new Error("target=both delivered different approval ids to channel and DM");
}
const reaction = await reactToApproval({
context,
decision: "allow-once",
roomId: context.roomId,
targetEventId: channelApproval.event.eventId,
});
const result = await waitForApprovalDecision({
const result = await resolveApprovalDecision({
approvalId,
context,
decision: "allow-once",
kind: "exec",
});
assertApprovalDecisionResult({
approvalId,
decision: "allow-once",
result,
});
assertApprovalResolveResult(result);
const lateDuplicate = await client.waitForOptionalRoomEvent({
observedEvents: context.observedEvents,
predicate: (event) =>
@@ -599,16 +616,14 @@ export async function runApprovalChannelTargetBothScenario(context: MatrixQaScen
buildMatrixApprovalArtifact(channelApproval.event),
buildMatrixApprovalArtifact(dmApproval.event),
],
reactionEmoji: reaction.reaction?.key,
reactionEventId: reaction.eventId,
reactionTargetEventId: reaction.reaction?.eventId,
resolveResult: result,
token,
},
details: [
`channel approval event: ${channelApproval.event.eventId}`,
`dm approval event: ${dmApproval.event.eventId}`,
`approval id: ${approvalId}`,
`decision: allow-once`,
`decision: allow-once via gateway resolve`,
].join("\n"),
} satisfies MatrixQaScenarioExecution;
}