package storesqlite import ( "context" "database/sql" "errors" "fmt" "strings" ) func (s *Store) ListEffectiveSessionTurns(ctx context.Context, workspaceID, agentSessionID string) ([]Turn, error) { if s == nil || s.db == nil { return nil, errors.New("workspace database is not initialized") } workspaceID, agentSessionID = strings.TrimSpace(workspaceID), strings.TrimSpace(agentSessionID) if workspaceID == "" || agentSessionID == "" { return nil, nil } rows, err := s.db.QueryContext(ctx, agentTurnSelectSQL+` WHERE workspace_id = ? AND agent_session_id = ? AND EXISTS ( SELECT 1 FROM workspace_agent_turn_history AS history WHERE history.workspace_id = workspace_agent_turns.workspace_id AND history.agent_session_id = workspace_agent_turns.agent_session_id AND history.turn_id = workspace_agent_turns.turn_id AND history.history_state = 'effective' ) ORDER BY started_at_unix_ms ASC, turn_id ASC `, workspaceID, agentSessionID) if err != nil { return nil, fmt.Errorf("list effective workspace agent turns: %w", err) } defer rows.Close() result := make([]Turn, 0) for rows.Next() { turn, err := scanAgentTurn(rows) if err != nil { return nil, err } result = append(result, turn) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate effective workspace agent turns: %w", err) } return result, nil } func (s *Store) MarkEditRetryRollbackDispatched(ctx context.Context, input MarkEditRetryRollbackDispatchedInput) (RuntimeOperation, bool, error) { if s == nil || s.db == nil { return RuntimeOperation{}, false, errors.New("workspace database is not initialized") } input.WorkspaceID = strings.TrimSpace(input.WorkspaceID) input.OperationID = strings.TrimSpace(input.OperationID) input.LeaseOwner = strings.TrimSpace(input.LeaseOwner) if input.WorkspaceID == "" || input.OperationID == "" || input.LeaseOwner == "" || input.NowUnixMS <= 0 { return RuntimeOperation{}, false, errors.New("workspace, operation, owner, and rollback dispatch time are required") } if input.Payload.Checkpoint != EditRetryCheckpointRollbackDispatched { return RuntimeOperation{}, false, ErrRuntimeOperationSubjectState } payload, err := editRetryPayloadMap(input.OperationID, input.Payload) if err != nil { return RuntimeOperation{}, false, err } payloadJSON, err := marshalJSONMap(payload) if err != nil { return RuntimeOperation{}, false, err } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("begin edit retry rollback dispatch: %w", err) } committed := false defer func() { if !committed { _ = tx.Rollback() } }() op, found, err := getRuntimeOperationTx(ctx, tx, input.WorkspaceID, input.OperationID) if err != nil { return RuntimeOperation{}, false, err } if !validEditRetryLease(op, found, input.LeaseOwner, input.NowUnixMS) { return op, false, ErrRuntimeOperationLeaseLost } current, err := DecodeEditRetryOperationPayload(op.Payload) if err != nil { return op, false, err } if !editRetryIdentityEqual(current, input.Payload) { return op, false, ErrRuntimeOperationSubjectState } if current.Checkpoint == EditRetryCheckpointRollbackDispatched { if _, err := s.commitTransaction(ctx, tx, input.WorkspaceID, nil); err != nil { return RuntimeOperation{}, false, err } committed = true return op, false, nil } if current.Checkpoint != EditRetryCheckpointPrepared { return op, false, ErrRuntimeOperationSubjectState } update, err := tx.ExecContext(ctx, ` UPDATE workspace_agent_session_history SET recovery_state = 'rollback_pending', operation_id = ?, updated_at_unix_ms = ? WHERE workspace_id = ? AND agent_session_id = ? AND history_revision = ? AND recovery_state = 'ready' `, op.OperationID, input.NowUnixMS, op.WorkspaceID, op.AgentSessionID, current.ExpectedRevision) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("fence edit retry rollback dispatch: %w", err) } if changed, err := rowsWereAffected(update, "fence edit retry rollback dispatch"); err != nil || !changed { return RuntimeOperation{}, false, ErrRuntimeOperationSubjectState } update, err = tx.ExecContext(ctx, ` UPDATE workspace_agent_runtime_operations SET payload_json = ?, version = version + 1, updated_at_unix_ms = ? WHERE workspace_id = ? AND operation_id = ? AND status = ? AND lease_owner = ? `, payloadJSON, input.NowUnixMS, op.WorkspaceID, op.OperationID, RuntimeOperationStatusLeased, input.LeaseOwner) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("checkpoint edit retry rollback dispatch: %w", err) } if changed, err := rowsWereAffected(update, "checkpoint edit retry rollback dispatch"); err != nil || !changed { return RuntimeOperation{}, false, ErrRuntimeOperationLeaseLost } op.Payload = payload event, err := insertRuntimeOperationEventTx(ctx, tx, op, RuntimeOperationEventEditRetryPending, map[string]any{ "turnId": op.TurnID, "historyRevision": current.ExpectedRevision, }, input.NowUnixMS) if err != nil { return RuntimeOperation{}, false, err } op, _, err = getRuntimeOperationTx(ctx, tx, op.WorkspaceID, op.OperationID) if err != nil { return RuntimeOperation{}, false, err } delta, err := s.commitTransaction(ctx, tx, op.WorkspaceID, []TransactionMutation{ transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntitySession, op.AgentSessionID, "history_rollback_pending", current.ExpectedRevision), transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntityRuntimeOperation, op.OperationID, "checkpoint", op.Version), transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntityRuntimeEvent, fmt.Sprint(event.ID), "insert", event.ID), }) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("commit edit retry rollback dispatch: %w", err) } committed = true op.CommitTransactionID, op.CommitDelta = delta.TransactionID, delta return op, true, nil } func (s *Store) ConfirmEditRetryRollback(ctx context.Context, input ConfirmEditRetryRollbackInput) (RuntimeOperation, bool, error) { if s == nil || s.db == nil { return RuntimeOperation{}, false, errors.New("workspace database is not initialized") } input.WorkspaceID = strings.TrimSpace(input.WorkspaceID) input.OperationID = strings.TrimSpace(input.OperationID) input.LeaseOwner = strings.TrimSpace(input.LeaseOwner) if input.WorkspaceID == "" || input.OperationID == "" || input.LeaseOwner == "" || input.NowUnixMS <= 0 { return RuntimeOperation{}, false, errors.New("workspace, operation, owner, and rollback confirmation time are required") } if input.Payload.Checkpoint != EditRetryCheckpointRollbackConfirmed { return RuntimeOperation{}, false, ErrRuntimeOperationSubjectState } payload, err := editRetryPayloadMap(input.OperationID, input.Payload) if err != nil { return RuntimeOperation{}, false, err } payloadJSON, err := marshalJSONMap(payload) if err != nil { return RuntimeOperation{}, false, err } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("begin edit retry rollback confirmation: %w", err) } committed := false defer func() { if !committed { _ = tx.Rollback() } }() op, found, err := getRuntimeOperationTx(ctx, tx, input.WorkspaceID, input.OperationID) if err != nil { return RuntimeOperation{}, false, err } if !validEditRetryLease(op, found, input.LeaseOwner, input.NowUnixMS) { return op, false, ErrRuntimeOperationLeaseLost } current, err := DecodeEditRetryOperationPayload(op.Payload) if err != nil { return op, false, err } if !editRetryIdentityEqual(current, input.Payload) { return op, false, ErrRuntimeOperationSubjectState } if current.Checkpoint == EditRetryCheckpointRollbackConfirmed { if _, err := s.commitTransaction(ctx, tx, input.WorkspaceID, nil); err != nil { return RuntimeOperation{}, false, err } committed = true return op, false, nil } if current.Checkpoint != EditRetryCheckpointRollbackDispatched || len(current.BeforeProviderIDs) == 0 || !equalStringValues(current.BeforeProviderIDs[:len(current.BeforeProviderIDs)-1], input.ProviderTurnIDs) { return op, false, ErrRuntimeOperationSubjectState } turn, found, err := getAgentTurnTx(ctx, tx, op.WorkspaceID, op.AgentSessionID, op.TurnID) if err != nil { return RuntimeOperation{}, false, err } if !found || strings.TrimSpace(turn.RootProviderTurnID) != current.BeforeProviderIDs[len(current.BeforeProviderIDs)-1] { return op, false, ErrRuntimeOperationSubjectState } update, err := tx.ExecContext(ctx, ` UPDATE workspace_agent_turn_history SET history_state = 'retracted', retracted_by_operation_id = ?, replacement_turn_id = '', updated_at_unix_ms = ? WHERE workspace_id = ? AND agent_session_id = ? AND turn_id = ? AND history_state = 'effective' `, op.OperationID, input.NowUnixMS, op.WorkspaceID, op.AgentSessionID, op.TurnID) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("retract edit retry turn: %w", err) } if changed, err := rowsWereAffected(update, "retract edit retry turn"); err != nil || !changed { return RuntimeOperation{}, false, ErrRuntimeOperationSubjectState } update, err = tx.ExecContext(ctx, ` UPDATE workspace_agent_session_history SET history_revision = history_revision + 1, recovery_state = 'resend_pending', operation_id = ?, updated_at_unix_ms = ? WHERE workspace_id = ? AND agent_session_id = ? AND history_revision = ? AND recovery_state = 'rollback_pending' AND operation_id = ? `, op.OperationID, input.NowUnixMS, op.WorkspaceID, op.AgentSessionID, current.ExpectedRevision, op.OperationID) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("advance edit retry history revision: %w", err) } if changed, err := rowsWereAffected(update, "advance edit retry history revision"); err != nil || !changed { return RuntimeOperation{}, false, ErrRuntimeOperationSubjectState } update, err = tx.ExecContext(ctx, ` UPDATE workspace_agent_runtime_operations SET payload_json = ?, version = version + 1, updated_at_unix_ms = ? WHERE workspace_id = ? AND operation_id = ? AND status = ? AND lease_owner = ? `, payloadJSON, input.NowUnixMS, op.WorkspaceID, op.OperationID, RuntimeOperationStatusLeased, input.LeaseOwner) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("checkpoint confirmed edit retry rollback: %w", err) } if changed, err := rowsWereAffected(update, "checkpoint confirmed edit retry rollback"); err != nil || !changed { return RuntimeOperation{}, false, ErrRuntimeOperationLeaseLost } op.Payload = payload event, err := insertRuntimeOperationEventTx(ctx, tx, op, RuntimeOperationEventEditRetryRollback, map[string]any{ "turnId": op.TurnID, "historyRevision": current.ExpectedRevision + 1, "providerTurnIds": append([]string(nil), input.ProviderTurnIDs...), }, input.NowUnixMS) if err != nil { return RuntimeOperation{}, false, err } op, _, err = getRuntimeOperationTx(ctx, tx, op.WorkspaceID, op.OperationID) if err != nil { return RuntimeOperation{}, false, err } delta, err := s.commitTransaction(ctx, tx, op.WorkspaceID, []TransactionMutation{ transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntityTurn, op.TurnID, "retract", input.NowUnixMS), transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntitySession, op.AgentSessionID, "history_replace", current.ExpectedRevision+1), transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntityRuntimeOperation, op.OperationID, "checkpoint", op.Version), transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntityRuntimeEvent, fmt.Sprint(event.ID), "insert", event.ID), }) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("commit edit retry rollback confirmation: %w", err) } committed = true op.CommitTransactionID, op.CommitDelta = delta.TransactionID, delta return op, true, nil } // AbortEditRetryRollback is permitted only after an authoritative provider // read proves membership still equals the pre-dispatch checkpoint. func (s *Store) AbortEditRetryRollback(ctx context.Context, input AbortEditRetryRollbackInput) (RuntimeOperation, bool, error) { if s == nil || s.db == nil { return RuntimeOperation{}, false, errors.New("workspace database is not initialized") } input.WorkspaceID = strings.TrimSpace(input.WorkspaceID) input.OperationID = strings.TrimSpace(input.OperationID) input.LeaseOwner = strings.TrimSpace(input.LeaseOwner) reason, err := editRetryReason(input.ReasonCode, input.Reason) if err != nil || input.WorkspaceID == "" || input.OperationID == "" || input.LeaseOwner == "" || input.NowUnixMS <= 0 || len(input.ProviderTurnIDs) == 0 { return RuntimeOperation{}, false, errors.New("valid edit retry rollback abort input is required") } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("begin edit retry rollback abort: %w", err) } committed := false defer func() { if !committed { _ = tx.Rollback() } }() op, found, err := getRuntimeOperationTx(ctx, tx, input.WorkspaceID, input.OperationID) if err != nil { return RuntimeOperation{}, false, err } if !validEditRetryLease(op, found, input.LeaseOwner, input.NowUnixMS) { return op, false, ErrRuntimeOperationLeaseLost } payload, err := DecodeEditRetryOperationPayload(op.Payload) if err != nil { return op, false, err } if payload.Checkpoint != EditRetryCheckpointRollbackDispatched || !equalStringValues(payload.BeforeProviderIDs, input.ProviderTurnIDs) { return op, false, ErrRuntimeOperationSubjectState } update, err := tx.ExecContext(ctx, ` UPDATE workspace_agent_session_history SET recovery_state = 'ready', operation_id = '', updated_at_unix_ms = ? WHERE workspace_id = ? AND agent_session_id = ? AND history_revision = ? AND recovery_state = 'rollback_pending' AND operation_id = ? `, input.NowUnixMS, op.WorkspaceID, op.AgentSessionID, payload.ExpectedRevision, op.OperationID) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("restore edit retry history after rejected rollback: %w", err) } if changed, err := rowsWereAffected(update, "restore edit retry history after rejected rollback"); err != nil || !changed { return RuntimeOperation{}, false, ErrRuntimeOperationSubjectState } payload.Checkpoint = EditRetryCheckpointRollbackAborted payloadMap, err := EncodeEditRetryOperationPayload(payload) if err != nil { return RuntimeOperation{}, false, err } payloadJSON, err := marshalJSONMap(payloadMap) if err != nil { return RuntimeOperation{}, false, err } update, err = tx.ExecContext(ctx, ` UPDATE workspace_agent_runtime_operations SET status = 'failed', result = 'failed', payload_json = ?, lease_owner = NULL, lease_expires_at_unix_ms = NULL, next_attempt_at_unix_ms = NULL, version = version + 1, last_error = ?, updated_at_unix_ms = ? WHERE workspace_id = ? AND operation_id = ? AND status = 'leased' AND lease_owner = ? `, payloadJSON, string(reason), input.NowUnixMS, op.WorkspaceID, op.OperationID, input.LeaseOwner) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("fail rejected edit retry rollback: %w", err) } if changed, err := rowsWereAffected(update, "fail rejected edit retry rollback"); err != nil || !changed { return RuntimeOperation{}, false, ErrRuntimeOperationLeaseLost } op, _, err = getRuntimeOperationTx(ctx, tx, op.WorkspaceID, op.OperationID) if err != nil { return RuntimeOperation{}, false, err } delta, err := s.commitTransaction(ctx, tx, op.WorkspaceID, []TransactionMutation{ transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntitySession, op.AgentSessionID, "history_rollback_aborted", payload.ExpectedRevision), transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntityRuntimeOperation, op.OperationID, "fail", op.Version), }) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("commit edit retry rollback abort: %w", err) } committed = true op.CommitTransactionID, op.CommitDelta = delta.TransactionID, delta return op, true, nil } func (s *Store) CompleteEditRetryRuntimeOperation(ctx context.Context, input CompleteEditRetryRuntimeOperationInput) (RuntimeOperationCompletion, bool, error) { input.ReplacementTurnID = strings.TrimSpace(input.ReplacementTurnID) input.ProviderTurnID = strings.TrimSpace(input.ProviderTurnID) if input.ReplacementTurnID == "" || input.ProviderTurnID == "" { return RuntimeOperationCompletion{}, false, errors.New("edit retry replacement turn and provider turn are required") } return s.completeRuntimeOperation(ctx, input.WorkspaceID, input.OperationID, input.LeaseOwner, input.NowUnixMS, func(tx *sql.Tx, op RuntimeOperation) (string, string, map[string]any, error) { payload, err := DecodeEditRetryOperationPayload(op.Payload) if err != nil { return "", "", nil, err } if op.Kind != RuntimeOperationKindEditRetry || payload.Checkpoint != EditRetryCheckpointReplacementDispatched || payload.ReplacementTurnID != input.ReplacementTurnID { return "", "", nil, ErrRuntimeOperationSubjectState } replacement, found, err := getAgentTurnTx(ctx, tx, op.WorkspaceID, op.AgentSessionID, input.ReplacementTurnID) if err != nil { return "", "", nil, err } // The canonical provider receipt and accepted submit claim are both // required; neither one alone proves the replacement was delivered. if !found || replacement.TurnID == op.TurnID || strings.TrimSpace(replacement.RootProviderTurnID) != input.ProviderTurnID { return "", "", nil, ErrRuntimeOperationSubjectState } var submissionClientSubmitID string if err := tx.QueryRowContext(ctx, ` SELECT client_submit_id FROM workspace_agent_turn_submissions WHERE workspace_id = ? AND agent_session_id = ? AND turn_id = ? `, op.WorkspaceID, op.AgentSessionID, replacement.TurnID).Scan(&submissionClientSubmitID); err != nil { return "", "", nil, fmt.Errorf("read edit retry replacement submission: %w", err) } if strings.TrimSpace(submissionClientSubmitID) != payload.ClientSubmitID { return "", "", nil, ErrRuntimeOperationSubjectState } var claimStatus string var claimCanonicalTurnID, claimTurnID sql.NullString if err := tx.QueryRowContext(ctx, ` SELECT status, canonical_turn_id, turn_id FROM workspace_agent_submit_claims WHERE workspace_id = ? AND agent_session_id = ? AND client_submit_id = ? `, op.WorkspaceID, op.AgentSessionID, payload.ClientSubmitID).Scan( &claimStatus, &claimCanonicalTurnID, &claimTurnID, ); err != nil { if errors.Is(err, sql.ErrNoRows) { return "", "", nil, ErrRuntimeOperationSubjectState } return "", "", nil, fmt.Errorf("read edit retry replacement submit claim: %w", err) } if claimStatus != "accepted" || !claimCanonicalTurnID.Valid || strings.TrimSpace(claimCanonicalTurnID.String) != replacement.TurnID || !claimTurnID.Valid || strings.TrimSpace(claimTurnID.String) != replacement.TurnID { return "", "", nil, ErrRuntimeOperationSubjectState } update, err := tx.ExecContext(ctx, ` UPDATE workspace_agent_turn_history SET replacement_turn_id = ?, updated_at_unix_ms = ? WHERE workspace_id = ? AND agent_session_id = ? AND turn_id = ? AND history_state = 'retracted' AND retracted_by_operation_id = ? `, replacement.TurnID, input.NowUnixMS, op.WorkspaceID, op.AgentSessionID, op.TurnID, op.OperationID) if err != nil { return "", "", nil, fmt.Errorf("link edit retry replacement turn: %w", err) } if changed, err := rowsWereAffected(update, "link edit retry replacement turn"); err != nil || !changed { return "", "", nil, ErrRuntimeOperationSubjectState } update, err = tx.ExecContext(ctx, ` UPDATE workspace_agent_session_history SET history_revision = history_revision + 1, recovery_state = 'ready', operation_id = '', updated_at_unix_ms = ? WHERE workspace_id = ? AND agent_session_id = ? AND history_revision = ? AND recovery_state = 'resend_pending' AND operation_id = ? `, input.NowUnixMS, op.WorkspaceID, op.AgentSessionID, payload.ExpectedRevision+1, op.OperationID) if err != nil { return "", "", nil, fmt.Errorf("complete edit retry session history: %w", err) } if changed, err := rowsWereAffected(update, "complete edit retry session history"); err != nil || !changed { return "", "", nil, ErrRuntimeOperationSubjectState } return RuntimeOperationResultApplied, RuntimeOperationEventEditRetryCompleted, map[string]any{ "turnId": op.TurnID, "replacementTurnId": replacement.TurnID, "providerTurnId": input.ProviderTurnID, "historyRevision": payload.ExpectedRevision + 2, }, nil }) } func (s *Store) FailEditRetryRecovery(ctx context.Context, input FailEditRetryRecoveryInput) (RuntimeOperation, bool, error) { if s == nil || s.db == nil { return RuntimeOperation{}, false, errors.New("workspace database is not initialized") } input.WorkspaceID = strings.TrimSpace(input.WorkspaceID) input.OperationID = strings.TrimSpace(input.OperationID) input.LeaseOwner = strings.TrimSpace(input.LeaseOwner) reason, err := editRetryReason(input.ReasonCode, input.Reason) if err != nil || input.WorkspaceID == "" || input.OperationID == "" || input.LeaseOwner == "" || input.NowUnixMS <= 0 { return RuntimeOperation{}, false, errors.New("valid edit retry recovery failure input is required") } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("begin edit retry recovery failure: %w", err) } committed := false defer func() { if !committed { _ = tx.Rollback() } }() op, found, err := getRuntimeOperationTx(ctx, tx, input.WorkspaceID, input.OperationID) if err != nil { return RuntimeOperation{}, false, err } if !validEditRetryLease(op, found, input.LeaseOwner, input.NowUnixMS) { return op, false, ErrRuntimeOperationLeaseLost } update, err := tx.ExecContext(ctx, ` UPDATE workspace_agent_session_history SET recovery_state = 'recovery_required', operation_id = ?, updated_at_unix_ms = ? WHERE workspace_id = ? AND agent_session_id = ? `, op.OperationID, input.NowUnixMS, op.WorkspaceID, op.AgentSessionID) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("mark edit retry session recovery required: %w", err) } if changed, err := rowsWereAffected(update, "mark edit retry session recovery required"); err != nil || !changed { return RuntimeOperation{}, false, ErrRuntimeOperationSubjectState } var revision int64 if err := tx.QueryRowContext(ctx, ` SELECT history_revision FROM workspace_agent_session_history WHERE workspace_id = ? AND agent_session_id = ? `, op.WorkspaceID, op.AgentSessionID).Scan(&revision); err != nil { return RuntimeOperation{}, false, fmt.Errorf("read edit retry recovery history revision: %w", err) } update, err = tx.ExecContext(ctx, ` UPDATE workspace_agent_runtime_operations SET status = 'failed', result = 'failed', lease_owner = NULL, lease_expires_at_unix_ms = NULL, next_attempt_at_unix_ms = NULL, version = version + 1, last_error = ?, updated_at_unix_ms = ? WHERE workspace_id = ? AND operation_id = ? AND status = 'leased' AND lease_owner = ? `, string(reason), input.NowUnixMS, op.WorkspaceID, op.OperationID, input.LeaseOwner) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("fail edit retry recovery operation: %w", err) } if changed, err := rowsWereAffected(update, "fail edit retry recovery operation"); err != nil || !changed { return RuntimeOperation{}, false, ErrRuntimeOperationLeaseLost } event, err := insertRuntimeOperationEventTx(ctx, tx, op, RuntimeOperationEventEditRetryRecovery, map[string]any{ "turnId": op.TurnID, "reasonCode": string(reason), "historyRevision": revision, }, input.NowUnixMS) if err != nil { return RuntimeOperation{}, false, err } op, _, err = getRuntimeOperationTx(ctx, tx, op.WorkspaceID, op.OperationID) if err != nil { return RuntimeOperation{}, false, err } delta, err := s.commitTransaction(ctx, tx, op.WorkspaceID, []TransactionMutation{ transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntitySession, op.AgentSessionID, "history_recovery_required", revision), transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntityRuntimeOperation, op.OperationID, "fail", op.Version), transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntityRuntimeEvent, fmt.Sprint(event.ID), "insert", event.ID), }) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("commit edit retry recovery failure: %w", err) } committed = true op.CommitTransactionID, op.CommitDelta = delta.TransactionID, delta return op, true, nil } // QuarantineEditRetryOperation abandons a stuck edit-retry operation: it fails // the runtime operation AND clears the session's effective-history fence back to // ready (dropping operation_id), in one transaction. It is used when durable // edit-retry is disabled — the operation can never make progress, so leaving the // session fenced at rollback_pending/resend_pending/recovery_required would // permanently block every subsequent send (requireSendAllowedByEffectiveHistory). // Unlike AbortEditRetryRollback it tolerates any fenced state and does not restore // the retracted turn; the edit is abandoned so the conversation becomes usable. func (s *Store) QuarantineEditRetryOperation(ctx context.Context, input QuarantineEditRetryOperationInput) (RuntimeOperation, bool, error) { if s == nil || s.db == nil { return RuntimeOperation{}, false, errors.New("workspace database is not initialized") } input.WorkspaceID = strings.TrimSpace(input.WorkspaceID) input.OperationID = strings.TrimSpace(input.OperationID) input.LeaseOwner = strings.TrimSpace(input.LeaseOwner) if input.WorkspaceID == "" || input.OperationID == "" || input.LeaseOwner == "" || input.NowUnixMS <= 0 { return RuntimeOperation{}, false, errors.New("valid edit retry quarantine input is required") } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("begin edit retry quarantine: %w", err) } committed := false defer func() { if !committed { _ = tx.Rollback() } }() op, found, err := getRuntimeOperationTx(ctx, tx, input.WorkspaceID, input.OperationID) if err != nil { return RuntimeOperation{}, false, err } if !validEditRetryLease(op, found, input.LeaseOwner, input.NowUnixMS) { return op, false, ErrRuntimeOperationLeaseLost } // Clear only the fence this operation owns so the session can send again. A // fence owned by a newer operation (or already ready) is left untouched — that // is not an error; the runtime operation is still abandoned below. if _, err := tx.ExecContext(ctx, ` UPDATE workspace_agent_session_history SET recovery_state = 'ready', operation_id = '', updated_at_unix_ms = ? WHERE workspace_id = ? AND agent_session_id = ? AND operation_id = ? AND recovery_state != 'ready' `, input.NowUnixMS, op.WorkspaceID, op.AgentSessionID, op.OperationID); err != nil { return RuntimeOperation{}, false, fmt.Errorf("clear edit retry history fence: %w", err) } var revision int64 if err := tx.QueryRowContext(ctx, ` SELECT COALESCE((SELECT history_revision FROM workspace_agent_session_history WHERE workspace_id = ? AND agent_session_id = ?), 0) `, op.WorkspaceID, op.AgentSessionID).Scan(&revision); err != nil { return RuntimeOperation{}, false, fmt.Errorf("read edit retry quarantine history revision: %w", err) } update, err := tx.ExecContext(ctx, ` UPDATE workspace_agent_runtime_operations SET status = 'failed', result = 'failed', lease_owner = NULL, lease_expires_at_unix_ms = NULL, next_attempt_at_unix_ms = NULL, version = version + 1, last_error = ?, updated_at_unix_ms = ? WHERE workspace_id = ? AND operation_id = ? AND status = 'leased' AND lease_owner = ? `, "edit_retry disabled; operation quarantined", input.NowUnixMS, op.WorkspaceID, op.OperationID, input.LeaseOwner) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("fail quarantined edit retry operation: %w", err) } if changed, err := rowsWereAffected(update, "fail quarantined edit retry operation"); err != nil || !changed { return RuntimeOperation{}, false, ErrRuntimeOperationLeaseLost } op, _, err = getRuntimeOperationTx(ctx, tx, op.WorkspaceID, op.OperationID) if err != nil { return RuntimeOperation{}, false, err } delta, err := s.commitTransaction(ctx, tx, op.WorkspaceID, []TransactionMutation{ transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntitySession, op.AgentSessionID, "history_edit_retry_quarantined", revision), transactionMutation(op.WorkspaceID, op.AgentSessionID, MutationEntityRuntimeOperation, op.OperationID, "fail", op.Version), }) if err != nil { return RuntimeOperation{}, false, fmt.Errorf("commit edit retry quarantine: %w", err) } committed = true op.CommitTransactionID, op.CommitDelta = delta.TransactionID, delta return op, true, nil } // ClearAbandonedEditRetryFence clears one session's effective-history fence back // to ready when the edit-retry operation that owns it can no longer make // progress: the owning operation is missing (e.g. removed by an older rescue // script) or already terminal (failed/completed). Fences owned by an in-flight // (prepared/leased) operation are left untouched — while durable edit-retry is // disabled those are quarantined by the recovery worker, which clears the fence // itself. This exists for sessions fenced before the feature was neutralized // (e.g. recovery_required from FailEditRetryRecovery, or a failed operation // quarantined without a fence clear); without it those sessions would reject // every send forever. Returns whether the fence was cleared. func (s *Store) ClearAbandonedEditRetryFence(ctx context.Context, input ClearAbandonedEditRetryFenceInput) (bool, error) { if s == nil || s.db == nil { return false, errors.New("workspace database is not initialized") } input.WorkspaceID = strings.TrimSpace(input.WorkspaceID) input.AgentSessionID = strings.TrimSpace(input.AgentSessionID) if input.WorkspaceID == "" || input.AgentSessionID == "" || input.NowUnixMS <= 0 { return false, errors.New("valid edit retry fence clear input is required") } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return false, fmt.Errorf("begin edit retry fence clear: %w", err) } committed := false defer func() { if !committed { _ = tx.Rollback() } }() update, err := tx.ExecContext(ctx, ` UPDATE workspace_agent_session_history SET recovery_state = 'ready', operation_id = '', updated_at_unix_ms = ? WHERE workspace_id = ? AND agent_session_id = ? AND recovery_state != 'ready' AND NOT EXISTS ( SELECT 1 FROM workspace_agent_runtime_operations o WHERE o.workspace_id = workspace_agent_session_history.workspace_id AND o.operation_id = workspace_agent_session_history.operation_id AND o.status IN ('prepared','leased')) `, input.NowUnixMS, input.WorkspaceID, input.AgentSessionID) if err != nil { return false, fmt.Errorf("clear abandoned edit retry fence: %w", err) } changed, err := rowsWereAffected(update, "clear abandoned edit retry fence") if err != nil { return false, err } if !changed { return false, nil } var revision int64 if err := tx.QueryRowContext(ctx, ` SELECT history_revision FROM workspace_agent_session_history WHERE workspace_id = ? AND agent_session_id = ? `, input.WorkspaceID, input.AgentSessionID).Scan(&revision); err != nil { return false, fmt.Errorf("read edit retry fence clear history revision: %w", err) } if _, err := s.commitTransaction(ctx, tx, input.WorkspaceID, []TransactionMutation{ transactionMutation(input.WorkspaceID, input.AgentSessionID, MutationEntitySession, input.AgentSessionID, "history_edit_retry_fence_cleared", revision), }); err != nil { return false, fmt.Errorf("commit edit retry fence clear: %w", err) } committed = true return true, nil } func validEditRetryLease(op RuntimeOperation, found bool, owner string, now int64) bool { return found && op.Kind == RuntimeOperationKindEditRetry && op.Status == RuntimeOperationStatusLeased && op.LeaseOwner == owner && op.LeaseExpiresAtMS > now } func editRetryIdentityEqual(left, right EditRetryOperationPayload) bool { return left.ClientOperationID == right.ClientOperationID && left.EditedText == right.EditedText && left.ReplacementTurnID == right.ReplacementTurnID && left.ClientSubmitID == right.ClientSubmitID && left.ExpectedRevision == right.ExpectedRevision } func equalStringValues(left, right []string) bool { if len(left) != len(right) { return false } for index := range left { if left[index] != strings.TrimSpace(right[index]) { return false } } return true }