Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
83 lines
2.1 KiB
Go
83 lines
2.1 KiB
Go
package storesqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
)
|
|
|
|
func activeSessionForkSourceOperationTx(
|
|
ctx context.Context,
|
|
tx *sql.Tx,
|
|
workspaceID, agentSessionID string,
|
|
) (string, bool, error) {
|
|
var operationID string
|
|
err := tx.QueryRowContext(ctx, `
|
|
SELECT operation_id
|
|
FROM workspace_agent_session_fork_operations
|
|
WHERE workspace_id = ?
|
|
AND source_agent_session_id = ?
|
|
AND status IN ('prepared','dispatching','provider_accepted')
|
|
LIMIT 1
|
|
`, workspaceID, agentSessionID).Scan(&operationID)
|
|
if err == sql.ErrNoRows {
|
|
return "", false, nil
|
|
}
|
|
if err != nil {
|
|
return "", false, fmt.Errorf("read active session fork source reservation: %w", err)
|
|
}
|
|
return operationID, true, nil
|
|
}
|
|
|
|
// requireSessionForkSourceWritableTx remains as the shared write-path hook, but
|
|
// optimistic Fork freezes its own canonical snapshot and never fences source
|
|
// activity. Physical cleanup is guarded separately by the retention check in
|
|
// requireSessionForkDeleteAllowedTx.
|
|
func requireSessionForkSourceWritableTx(
|
|
ctx context.Context,
|
|
tx *sql.Tx,
|
|
workspaceID, agentSessionID string,
|
|
) error {
|
|
_ = ctx
|
|
_ = tx
|
|
_ = workspaceID
|
|
_ = agentSessionID
|
|
return nil
|
|
}
|
|
|
|
func requireSessionForkDeleteAllowedTx(
|
|
ctx context.Context,
|
|
tx *sql.Tx,
|
|
workspaceID string,
|
|
agentSessionIDs []string,
|
|
) error {
|
|
for _, agentSessionID := range normalizedSessionIDs(agentSessionIDs) {
|
|
if _, active, err := activeSessionForkSourceOperationTx(
|
|
ctx, tx, workspaceID, agentSessionID,
|
|
); err != nil {
|
|
return err
|
|
} else if active {
|
|
return ErrSessionForkInProgress
|
|
}
|
|
var status string
|
|
err := tx.QueryRowContext(ctx, `
|
|
SELECT operation.status
|
|
FROM workspace_agent_session_fork_target_reservations reservation
|
|
JOIN workspace_agent_session_fork_operations operation
|
|
ON operation.operation_id = reservation.operation_id
|
|
WHERE reservation.workspace_id = ?
|
|
AND reservation.target_agent_session_id = ?
|
|
`, workspaceID, agentSessionID).Scan(&status)
|
|
if err == sql.ErrNoRows {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("read session fork target reservation for delete: %w", err)
|
|
}
|
|
if status != SessionForkStatusCommitted {
|
|
return ErrSessionForkTargetReserved
|
|
}
|
|
}
|
|
return nil
|
|
}
|