mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 11:13:43 +08:00
Foundation for the ghost-approval root-cause fix. Adds ResolveOutcome / MetadataDecision; rewrites ApprovalWorkflowService so every resolve / consume / timeout / supersede transitions through one two-phase contract: snapshot → DB UPDATE conditional on status=PENDING → metadata reconciliation → afterCommit memory mutation. ChatController, ChannelMessageRouter, and ApprovalController all switch to the workflow; ApprovalService.resolve / resolveAndConsume / consumeApproved / cancelStalePending / denyAllByConversation are physically removed so DB-bypass is no longer reachable at compile time. Specific fixes: - recoverFromDb preserves DB pendingId + createdAt (was generating fresh random ids, breaking every later DB sync) - effectiveExpireAt = expireAt ?? createdAt + PENDING_TTL: legacy rows with NULL expireAt no longer resurrect as live PENDING after restart - markPendingApprovalsResolved flips pendingApproval.status + currentPhase + MessageEntity.status atomically (was only flipping the first field; message.status uses existing completed/stopped, not approved/denied, to stay within the frontend Message.status union) - GC scheduler moves to ApprovalWorkflowService; timeouts and overflow evictions now sync DB + metadata + memory through markTimeout - DB UPDATE rows=0 returns alreadyResolved (concurrent-resolve safe); exception propagates so @Transactional rolls back; memory stays untouched - expireRecoveredRow gates metadata write on DB success (was writing metadata even when DB update failed, producing the worst-case ghost) - Mockito JDK 21 agent attach fixed via maven-dependency-plugin properties + surefire argLine (no more flaky self-attach across machines) Tests: 34 new across 4 classes (recovery, resolve, GC, metadata sync). Full suite: 788 / 788.
33 lines
1.5 KiB
Java
33 lines
1.5 KiB
Java
package vip.mate.approval;
|
|
|
|
/**
|
|
* Two-valued decision used when reconciling persisted approval state.
|
|
* <p>
|
|
* The frontend's {@code Message.status} union (mateclaw-ui/src/types/index.ts) only supports
|
|
* {@code generating | completed | stopped | failed | awaiting_approval | interrupted}, so
|
|
* approval decisions never appear at the message-status layer. {@code PendingApprovalMeta.status}
|
|
* holds the decision ({@code approved} / {@code denied}); when the message itself was
|
|
* persisted as {@code awaiting_approval} we collapse it back to one of the existing terminal
|
|
* message states ({@code completed} for approved, {@code stopped} for denied) so downstream
|
|
* consumers (history sanitizer, list ordering, stuck counters) keep working unchanged.
|
|
* <p>
|
|
* Timeout / superseded both map to {@link #DENIED} at the metadata layer (DB layer keeps
|
|
* the more specific {@code TIMEOUT} / {@code SUPERSEDED} status for audit purposes).
|
|
*/
|
|
public enum MetadataDecision {
|
|
|
|
APPROVED("approved", "completed"),
|
|
DENIED("denied", "stopped");
|
|
|
|
/** Target value for {@code metadata.pendingApproval.status}. */
|
|
public final String pendingApprovalStatus;
|
|
|
|
/** Target value for {@code MessageEntity.status} when source was {@code awaiting_approval}. */
|
|
public final String messageStatus;
|
|
|
|
MetadataDecision(String pendingApprovalStatus, String messageStatus) {
|
|
this.pendingApprovalStatus = pendingApprovalStatus;
|
|
this.messageStatus = messageStatus;
|
|
}
|
|
}
|