From fcfb6500da103a354e97b1475c46e3d6590b6e0b Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 18:29:15 -0700 Subject: [PATCH] test(qa): resolve Matrix target-both approvals via gateway --- CHANGELOG.md | 1 + .../contract/scenario-runtime-approval.ts | 47 ++++++++++++------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7528b297064..20d004dabc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/extensions/qa-matrix/src/runners/contract/scenario-runtime-approval.ts b/extensions/qa-matrix/src/runners/contract/scenario-runtime-approval.ts index d17a6d859ec..e90696c3b3d 100644 --- a/extensions/qa-matrix/src/runners/contract/scenario-runtime-approval.ts +++ b/extensions/qa-matrix/src/runners/contract/scenario-runtime-approval.ts @@ -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; }